Browse Source

Fix ll_queue callback signature and add debug utilities

Changes:
- ll_queue callback now receives only queue pointer and arg (no data)
- Added log_dump and addr_to_string utility functions
- Removed temporary backup files
- Updated AGENTS.md with additional guidelines
- Fixed compiler warnings in etcp.c and pkt_normalizer.c
nodeinfo-routing-update
Evgeny 8 months ago
parent
commit
41626a02c3
  1. 543
      1
  2. 2
      AGENTS.md
  3. 72
      lib/debug_config.c
  4. 435
      lib/debug_config.c1
  5. 8
      lib/debug_config.h
  6. 137
      lib/debug_config.h1
  7. 22
      lib/ll_queue.c
  8. 2
      lib/ll_queue.h
  9. 131
      lib/timeout_heap.c1
  10. 170
      lib/timeout_heap.c2
  11. 75
      lib/timeout_heap.h1
  12. 2
      lib/u_async.c
  13. 834
      lib/u_async.c.backup2
  14. 830
      lib/u_async.c1
  15. 800
      lib/u_async.c2
  16. 617
      lib/u_async.c3
  17. 69
      lib/u_async.h1
  18. 186
      src/etcp.c
  19. 6
      src/etcp.h
  20. 102
      src/etcp_connections.c
  21. 31
      src/etcp_loadbalancer.c
  22. 15
      src/pkt_normalizer.c
  23. 45
      tests/1
  24. BIN
      tests/test_config_debug
  25. BIN
      tests/test_crypto
  26. BIN
      tests/test_debug_categories
  27. BIN
      tests/test_ecc_encrypt
  28. BIN
      tests/test_etcp_crypto
  29. BIN
      tests/test_etcp_minimal
  30. BIN
      tests/test_etcp_simple_traffic
  31. 491
      tests/test_etcp_simple_traffic.c.backup
  32. 491
      tests/test_etcp_simple_traffic.c.orig
  33. BIN
      tests/test_etcp_two_instances
  34. 323
      tests/test_etcp_two_instances.c.backup
  35. 26
      tests/test_fix.patch
  36. BIN
      tests/test_intensive_memory_pool
  37. 157
      tests/test_intensive_memory_pool_backup.c
  38. BIN
      tests/test_ll_queue
  39. 8
      tests/test_ll_queue.c
  40. BIN
      tests/test_memory_pool_and_config
  41. BIN
      tests/test_packet_dump
  42. BIN
      tests/test_u_async_comprehensive
  43. BIN
      tests/test_u_async_performance

543
1

@ -11,3 +11,546 @@ timeout: не удалось выполнить команду «./test_etcp_two
timeout: не удалось выполнить команду «./test_etcp_two_instances»: Нет такого файла или каталога
timeout: не удалось выполнить команду «./test_etcp_two_instances»: Нет такого файла или каталога
timeout: не удалось выполнить команду «./test_etcp_two_instances»: Нет такого файла или каталога
cc1: fatal error: test_ll_queue_fixed.c: Нет такого файла или каталога
compilation terminated.
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-linux-gnu/13/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none:amdgcn-amdhsa
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 13.3.0-6ubuntu2~24.04' --with-bugurl=file:///usr/share/doc/gcc-13/README.Bugs --enable-languages=c,ada,c++,go,d,fortran,objc,obj-c++,m2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-13 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/libexec --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-libstdcxx-backtrace --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --enable-libphobos-checking=release --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --enable-cet --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-13-fG75Ri/gcc-13-13.3.0/debian/tmp-nvptx/usr,amdgcn-amdhsa=/build/gcc-13-fG75Ri/gcc-13-13.3.0/debian/tmp-gcn/usr --enable-offload-defaulted --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu --with-build-config=bootstrap-lto-lean --enable-link-serialization=2
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 13.3.0 (Ubuntu 13.3.0-6ubuntu2~24.04)
COLLECT_GCC_OPTIONS='-I' '/home/vnc1/proj/utun3/src' '-I' '/home/vnc1/proj/utun3/lib' '-I' '/home/vnc1/proj/utun3/tinycrypt/lib/include' '-I' '/home/vnc1/proj/utun3/tinycrypt/lib/source' '-g' '-O2' '-v' '-o' 'test_ll_queue_fixed' '-mtune=generic' '-march=x86-64' '-dumpdir' 'test_ll_queue_fixed-'
/usr/libexec/gcc/x86_64-linux-gnu/13/cc1 -quiet -v -I /home/vnc1/proj/utun3/src -I /home/vnc1/proj/utun3/lib -I /home/vnc1/proj/utun3/tinycrypt/lib/include -I /home/vnc1/proj/utun3/tinycrypt/lib/source -imultiarch x86_64-linux-gnu test_ll_queue_fixed.c -D_FORTIFY_SOURCE=3 -quiet -dumpdir test_ll_queue_fixed- -dumpbase test_ll_queue_fixed.c -dumpbase-ext .c -mtune=generic -march=x86-64 -g -O2 -version -fasynchronous-unwind-tables -fstack-protector-strong -Wformat -Wformat-security -fstack-clash-protection -fcf-protection -o /tmp/ccwoLaK6.s
GNU C17 (Ubuntu 13.3.0-6ubuntu2~24.04) version 13.3.0 (x86_64-linux-gnu)
compiled by GNU C version 13.3.0, GMP version 6.3.0, MPFR version 4.2.1, MPC version 1.3.1, isl version isl-0.26-GMP
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/13/include-fixed/x86_64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/13/include-fixed"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
/home/vnc1/proj/utun3/src
/home/vnc1/proj/utun3/lib
/home/vnc1/proj/utun3/tinycrypt/lib/include
/home/vnc1/proj/utun3/tinycrypt/lib/source
/usr/lib/gcc/x86_64-linux-gnu/13/include
/usr/local/include
/usr/include/x86_64-linux-gnu
/usr/include
End of search list.
cc1: fatal error: test_ll_queue_fixed.c: Нет такого файла или каталога
compilation terminated.
malloc(): corrupted top size
timeout: отслеживаемая команда завершилась созданием дампа
timeout: не удалось выполнить команду «./test_ll_queue_updated»: Нет такого файла или каталога
cc1: fatal error: test_ll_queue_updated.c: Нет такого файла или каталога
compilation terminated.
cc1: fatal error: test_ll_queue_updated.c: Нет такого файла или каталога
compilation terminated.
cc1: fatal error: test_ll_queue_updated.c: Нет такого файла или каталога
compilation terminated.
malloc(): corrupted top size
timeout: отслеживаемая команда завершилась созданием дампа
malloc(): corrupted top size
timeout: отслеживаемая команда завершилась созданием дампа
timeout: отслеживаемая команда завершилась созданием дампа
../build-aux/test-driver: строка 112: 1176142 Ошибка сегментирования (образ памяти сброшен на диск) "$@" >> "$log_file" 2>&1
make[3]: *** [Makefile:1223: test-suite.log] Ошибка 1
make[2]: *** [Makefile:1331: check-TESTS] Ошибка 2
make[1]: *** [Makefile:1495: check-am] Ошибка 2
make: *** [Makefile:375: check-recursive] Ошибка 1
free(): double free detected in tcache 2
timeout: отслеживаемая команда завершилась созданием дампа
ar: модификатор «u» игнорируется, так как по умолчанию используется «D» (смотрите «U»)
utun_instance.c: In function ‘utun_instance_create’:
utun_instance.c:109:53: warning: ‘/32’ directive output may be truncated writing 3 bytes into a region of size between 1 and 64 [-Wformat-truncation=]
109 | snprintf(tun_ip_str, sizeof(tun_ip_str), "%s/32", ip_buffer);
| ^~~
In file included from /usr/include/stdio.h:980,
from utun_instance.h:6,
from utun_instance.c:2:
In function ‘snprintf’,
inlined from ‘utun_instance_create’ at utun_instance.c:109:9:
/usr/include/x86_64-linux-gnu/bits/stdio2.h:54:10: note: ‘__builtin___snprintf_chk’ output between 4 and 67 bytes into a destination of size 64
54 | return __builtin___snprintf_chk (__s, __n, __USE_FORTIFY_LEVEL - 1,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
55 | __glibc_objsize (__s), __fmt,
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
56 | __va_arg_pack ());
| ~~~~~~~~~~~~~~~~~
In file included from config_parser.c:8:
config_parser.c: In function ‘parse_debug_categories’:
../lib/debug_config.h:42:36: warning: conversion from ‘long unsigned int’ to ‘uint32_t’ {aka ‘unsigned int’} changes value from ‘18446744073709551615’ to ‘4294967295’ [-Woverflow]
42 | #define DEBUG_CATEGORY_ALL (~((debug_category_t)0))
| ^
config_parser.c:38:29: note: in expansion of macro ‘DEBUG_CATEGORY_ALL’
38 | if (!value_copy) return DEBUG_CATEGORY_ALL; // Default to all on error
| ^~~~~~~~~~~~~~~~~~
../lib/debug_config.h:42:36: warning: conversion from ‘long unsigned int’ to ‘uint32_t’ {aka ‘unsigned int’} changes value from ‘18446744073709551615’ to ‘4294967295’ [-Woverflow]
42 | #define DEBUG_CATEGORY_ALL (~((debug_category_t)0))
| ^
config_parser.c:66:27: note: in expansion of macro ‘DEBUG_CATEGORY_ALL’
66 | categories |= DEBUG_CATEGORY_ALL;
| ^~~~~~~~~~~~~~~~~~
etcp.c: In function ‘etcp_connection_create’:
etcp.c:99:43: warning: passing argument 2 of ‘queue_set_callback’ from incompatible pointer type [-Wincompatible-pointer-types]
99 | queue_set_callback(etcp->input_queue, input_queue_cb, etcp);
| ^~~~~~~~~~~~~~
| |
| void (*)(struct ll_queue *, void *)
In file included from etcp.h:7,
from etcp.c:3:
../lib/ll_queue.h:83:63: note: expected ‘queue_callback_fn’ {aka ‘void (*)(struct ll_queue *, void *, void *)’} but argument is of type ‘void (*)(struct ll_queue *, void *)’
83 | void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
| ~~~~~~~~~~~~~~~~~~^~~~~~
etcp.c:100:44: warning: passing argument 2 of ‘queue_set_callback’ from incompatible pointer type [-Wincompatible-pointer-types]
100 | queue_set_callback(etcp->input_send_q, input_send_q_cb, etcp);
| ^~~~~~~~~~~~~~~
| |
| void (*)(struct ll_queue *, void *)
../lib/ll_queue.h:83:63: note: expected ‘queue_callback_fn’ {aka ‘void (*)(struct ll_queue *, void *, void *)’} but argument is of type ‘void (*)(struct ll_queue *, void *)’
83 | void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
| ~~~~~~~~~~~~~~~~~~^~~~~~
pkt_normalizer.c: In function ‘pkt_normalizer_send_service’:
pkt_normalizer.c:347:22: warning: initialization of ‘uint8_t *’ {aka ‘unsigned char *’} from incompatible pointer type ‘struct ll_entry *’ [-Wincompatible-pointer-types]
347 | uint8_t* d = (entry);
| ^
make[2]: *** Нет правила для сборки цели «test_ll_queue_working.c», требуемой для «test_ll_queue-test_ll_queue_working.o». Останов.
make[1]: *** [Makefile:1495: check-am] Ошибка 2
make: *** [Makefile:375: check-recursive] Ошибка 1
make[2]: *** Нет правила для сборки цели «test_ll_queue_working.c», требуемой для «test_ll_queue-test_ll_queue_working.o». Останов.
make[1]: *** [Makefile:1495: check-am] Ошибка 2
make: *** [Makefile:375: check-recursive] Ошибка 1
make[2]: *** Нет правила для сборки цели «test_ll_queue_working.c», требуемой для «test_ll_queue-test_ll_queue_working.o». Останов.
make[1]: *** [Makefile:1495: check-am] Ошибка 2
make: *** [Makefile:375: check-recursive] Ошибка 1
make[2]: *** Нет правила для сборки цели «test_ll_queue_new.c», требуемой для «test_ll_queue-test_ll_queue_new.o». Останов.
make[1]: *** [Makefile:1494: check-am] Ошибка 2
make: *** [Makefile:375: check-recursive] Ошибка 1
test_u_async_comprehensive.c: In function ‘test_concurrent_operations’:
test_u_async_comprehensive.c:428:13: warning: ignoring return value of ‘write’ declared with attribute ‘warn_unused_result’ [-Wunused-result]
428 | write(sockets[0], &data, 1);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from test_debug_categories.c:8:
test_debug_categories.c: In function ‘main’:
../lib/debug_config.h:42:36: warning: overflow in conversion from ‘long unsigned int’ to ‘int’ changes value from ‘18446744073709551615’ to ‘-1’ [-Woverflow]
42 | #define DEBUG_CATEGORY_ALL (~((debug_category_t)0))
| ^
test_debug_categories.c:64:32: note: in expansion of macro ‘DEBUG_CATEGORY_ALL’
64 | debug_categories = DEBUG_CATEGORY_ALL;
| ^~~~~~~~~~~~~~~~~~
make[3]: *** [Makefile:1223: test-suite.log] Ошибка 1
make[2]: *** [Makefile:1331: check-TESTS] Ошибка 2
make[1]: *** [Makefile:1495: check-am] Ошибка 2
make: *** [Makefile:375: check-recursive] Ошибка 1
make[3]: *** [Makefile:1223: test-suite.log] Ошибка 1
make[2]: *** [Makefile:1331: check-TESTS] Ошибка 2
make[1]: *** [Makefile:1495: check-am] Ошибка 2
make: *** [Makefile:375: check-recursive] Ошибка 1
make[2]: *** Нет правила для сборки цели «test_ll_queue_new.c», требуемой для «test_ll_queue-test_ll_queue_new.o». Останов.
make[1]: *** [Makefile:1494: check-am] Ошибка 2
make: *** [Makefile:375: check-recursive] Ошибка 1
make[2]: *** Нет правила для сборки цели «test_ll_queue_new.c», требуемой для «test_ll_queue-test_ll_queue_new.o». Останов.
make[1]: *** [Makefile:1494: check-am] Ошибка 2
make: *** [Makefile:375: check-recursive] Ошибка 1
make[2]: *** Нет правила для сборки цели «test_ll_queue_new.c», требуемой для «test_ll_queue-test_ll_queue_new.o». Останов.
make[1]: *** [Makefile:1494: check-am] Ошибка 2
make: *** [Makefile:375: check-recursive] Ошибка 1
ar: модификатор «u» игнорируется, так как по умолчанию используется «D» (смотрите «U»)
utun_instance.c: In function ‘utun_instance_create’:
utun_instance.c:109:53: warning: ‘/32’ directive output may be truncated writing 3 bytes into a region of size between 1 and 64 [-Wformat-truncation=]
109 | snprintf(tun_ip_str, sizeof(tun_ip_str), "%s/32", ip_buffer);
| ^~~
In file included from /usr/include/stdio.h:980,
from utun_instance.h:6,
from utun_instance.c:2:
In function ‘snprintf’,
inlined from ‘utun_instance_create’ at utun_instance.c:109:9:
/usr/include/x86_64-linux-gnu/bits/stdio2.h:54:10: note: ‘__builtin___snprintf_chk’ output between 4 and 67 bytes into a destination of size 64
54 | return __builtin___snprintf_chk (__s, __n, __USE_FORTIFY_LEVEL - 1,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
55 | __glibc_objsize (__s), __fmt,
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
56 | __va_arg_pack ());
| ~~~~~~~~~~~~~~~~~
In file included from config_parser.c:8:
config_parser.c: In function ‘parse_debug_categories’:
../lib/debug_config.h:42:36: warning: conversion from ‘long unsigned int’ to ‘uint32_t’ {aka ‘unsigned int’} changes value from ‘18446744073709551615’ to ‘4294967295’ [-Woverflow]
42 | #define DEBUG_CATEGORY_ALL (~((debug_category_t)0))
| ^
config_parser.c:38:29: note: in expansion of macro ‘DEBUG_CATEGORY_ALL’
38 | if (!value_copy) return DEBUG_CATEGORY_ALL; // Default to all on error
| ^~~~~~~~~~~~~~~~~~
../lib/debug_config.h:42:36: warning: conversion from ‘long unsigned int’ to ‘uint32_t’ {aka ‘unsigned int’} changes value from ‘18446744073709551615’ to ‘4294967295’ [-Woverflow]
42 | #define DEBUG_CATEGORY_ALL (~((debug_category_t)0))
| ^
config_parser.c:66:27: note: in expansion of macro ‘DEBUG_CATEGORY_ALL’
66 | categories |= DEBUG_CATEGORY_ALL;
| ^~~~~~~~~~~~~~~~~~
etcp.c: In function ‘etcp_connection_create’:
etcp.c:99:43: warning: passing argument 2 of ‘queue_set_callback’ from incompatible pointer type [-Wincompatible-pointer-types]
99 | queue_set_callback(etcp->input_queue, input_queue_cb, etcp);
| ^~~~~~~~~~~~~~
| |
| void (*)(struct ll_queue *, void *)
In file included from etcp.h:7,
from etcp.c:3:
../lib/ll_queue.h:83:63: note: expected ‘queue_callback_fn’ {aka ‘void (*)(struct ll_queue *, void *, void *)’} but argument is of type ‘void (*)(struct ll_queue *, void *)’
83 | void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
| ~~~~~~~~~~~~~~~~~~^~~~~~
etcp.c:100:44: warning: passing argument 2 of ‘queue_set_callback’ from incompatible pointer type [-Wincompatible-pointer-types]
100 | queue_set_callback(etcp->input_send_q, input_send_q_cb, etcp);
| ^~~~~~~~~~~~~~~
| |
| void (*)(struct ll_queue *, void *)
../lib/ll_queue.h:83:63: note: expected ‘queue_callback_fn’ {aka ‘void (*)(struct ll_queue *, void *, void *)’} but argument is of type ‘void (*)(struct ll_queue *, void *)’
83 | void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
| ~~~~~~~~~~~~~~~~~~^~~~~~
pkt_normalizer.c: In function ‘pkt_normalizer_send_service’:
pkt_normalizer.c:347:22: warning: initialization of ‘uint8_t *’ {aka ‘unsigned char *’} from incompatible pointer type ‘struct ll_entry *’ [-Wincompatible-pointer-types]
347 | uint8_t* d = (entry);
| ^
make[1]: *** [Makefile:375: all-recursive] Ошибка 1
make: *** [Makefile:316: all] Обрыв канала
make[2]: *** Нет правила для сборки цели «test_ll_queue_new.c», требуемой для «test_ll_queue-test_ll_queue_new.o». Останов.
make[1]: *** [Makefile:1494: check-am] Ошибка 2
make: *** [Makefile:375: check-recursive] Ошибка 1
make[2]: *** Нет правила для сборки цели «test_ll_queue_new.c», требуемой для «test_ll_queue-test_ll_queue_new.o». Останов.
make[1]: *** [Makefile:1494: check-am] Ошибка 2
make: *** [Makefile:375: check-recursive] Ошибка 1
make[2]: *** Нет правила для сборки цели «test_ll_queue_new.c», требуемой для «test_ll_queue-test_ll_queue_new.o». Останов.
make[1]: *** [Makefile:1494: check-am] Ошибка 2
make: *** [Makefile:375: check-recursive] Ошибка 1
ar: модификатор «u» игнорируется, так как по умолчанию используется «D» (смотрите «U»)
utun_instance.c: In function ‘utun_instance_create’:
utun_instance.c:109:53: warning: ‘/32’ directive output may be truncated writing 3 bytes into a region of size between 1 and 64 [-Wformat-truncation=]
109 | snprintf(tun_ip_str, sizeof(tun_ip_str), "%s/32", ip_buffer);
| ^~~
In file included from /usr/include/stdio.h:980,
from utun_instance.h:6,
from utun_instance.c:2:
In function ‘snprintf’,
inlined from ‘utun_instance_create’ at utun_instance.c:109:9:
/usr/include/x86_64-linux-gnu/bits/stdio2.h:54:10: note: ‘__builtin___snprintf_chk’ output between 4 and 67 bytes into a destination of size 64
54 | return __builtin___snprintf_chk (__s, __n, __USE_FORTIFY_LEVEL - 1,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
55 | __glibc_objsize (__s), __fmt,
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
56 | __va_arg_pack ());
| ~~~~~~~~~~~~~~~~~
In file included from config_parser.c:8:
config_parser.c: In function ‘parse_debug_categories’:
../lib/debug_config.h:42:36: warning: conversion from ‘long unsigned int’ to ‘uint32_t’ {aka ‘unsigned int’} changes value from ‘18446744073709551615’ to ‘4294967295’ [-Woverflow]
42 | #define DEBUG_CATEGORY_ALL (~((debug_category_t)0))
| ^
config_parser.c:38:29: note: in expansion of macro ‘DEBUG_CATEGORY_ALL’
38 | if (!value_copy) return DEBUG_CATEGORY_ALL; // Default to all on error
| ^~~~~~~~~~~~~~~~~~
../lib/debug_config.h:42:36: warning: conversion from ‘long unsigned int’ to ‘uint32_t’ {aka ‘unsigned int’} changes value from ‘18446744073709551615’ to ‘4294967295’ [-Woverflow]
42 | #define DEBUG_CATEGORY_ALL (~((debug_category_t)0))
| ^
config_parser.c:66:27: note: in expansion of macro ‘DEBUG_CATEGORY_ALL’
66 | categories |= DEBUG_CATEGORY_ALL;
| ^~~~~~~~~~~~~~~~~~
etcp.c: In function ‘etcp_connection_create’:
etcp.c:99:43: warning: passing argument 2 of ‘queue_set_callback’ from incompatible pointer type [-Wincompatible-pointer-types]
99 | queue_set_callback(etcp->input_queue, input_queue_cb, etcp);
| ^~~~~~~~~~~~~~
| |
| void (*)(struct ll_queue *, void *)
In file included from etcp.h:7,
from etcp.c:3:
../lib/ll_queue.h:83:63: note: expected ‘queue_callback_fn’ {aka ‘void (*)(struct ll_queue *, void *, void *)’} but argument is of type ‘void (*)(struct ll_queue *, void *)’
83 | void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
| ~~~~~~~~~~~~~~~~~~^~~~~~
etcp.c:100:44: warning: passing argument 2 of ‘queue_set_callback’ from incompatible pointer type [-Wincompatible-pointer-types]
100 | queue_set_callback(etcp->input_send_q, input_send_q_cb, etcp);
| ^~~~~~~~~~~~~~~
| |
| void (*)(struct ll_queue *, void *)
../lib/ll_queue.h:83:63: note: expected ‘queue_callback_fn’ {aka ‘void (*)(struct ll_queue *, void *, void *)’} but argument is of type ‘void (*)(struct ll_queue *, void *)’
83 | void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
| ~~~~~~~~~~~~~~~~~~^~~~~~
pkt_normalizer.c: In function ‘pkt_normalizer_send_service’:
pkt_normalizer.c:347:22: warning: initialization of ‘uint8_t *’ {aka ‘unsigned char *’} from incompatible pointer type ‘struct ll_entry *’ [-Wincompatible-pointer-types]
347 | uint8_t* d = (entry);
| ^
test_ll_queue.c:20: warning: "DEBUG_CATEGORY_LL_QUEUE" redefined
20 | #define DEBUG_CATEGORY_LL_QUEUE 1
|
In file included from test_ll_queue.c:16:
../lib/debug_config.h:32: note: this is the location of the previous definition
32 | #define DEBUG_CATEGORY_LL_QUEUE ((debug_category_t)1 << 1) // ll_queue module
|
test_u_async_comprehensive.c: In function ‘test_concurrent_operations’:
test_u_async_comprehensive.c:428:13: warning: ignoring return value of ‘write’ declared with attribute ‘warn_unused_result’ [-Wunused-result]
428 | write(sockets[0], &data, 1);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from test_debug_categories.c:8:
test_debug_categories.c: In function ‘main’:
../lib/debug_config.h:42:36: warning: overflow in conversion from ‘long unsigned int’ to ‘int’ changes value from ‘18446744073709551615’ to ‘-1’ [-Woverflow]
42 | #define DEBUG_CATEGORY_ALL (~((debug_category_t)0))
| ^
test_debug_categories.c:64:32: note: in expansion of macro ‘DEBUG_CATEGORY_ALL’
64 | debug_categories = DEBUG_CATEGORY_ALL;
| ^~~~~~~~~~~~~~~~~~
../build-aux/test-driver: строка 112: 1197495 Ошибка сегментирования (образ памяти сброшен на диск) "$@" >> "$log_file" 2>&1
make[3]: *** [Makefile:1223: test-suite.log] Ошибка 1
make[2]: *** [Makefile:1331: check-TESTS] Ошибка 2
make[1]: *** [Makefile:1495: check-am] Ошибка 2
make: *** [Makefile:375: check-recursive] Ошибка 1
ar: модификатор «u» игнорируется, так как по умолчанию используется «D» (смотрите «U»)
utun_instance.c: In function ‘utun_instance_create’:
utun_instance.c:109:53: warning: ‘/32’ directive output may be truncated writing 3 bytes into a region of size between 1 and 64 [-Wformat-truncation=]
109 | snprintf(tun_ip_str, sizeof(tun_ip_str), "%s/32", ip_buffer);
| ^~~
In file included from /usr/include/stdio.h:980,
from utun_instance.h:6,
from utun_instance.c:2:
In function ‘snprintf’,
inlined from ‘utun_instance_create’ at utun_instance.c:109:9:
/usr/include/x86_64-linux-gnu/bits/stdio2.h:54:10: note: ‘__builtin___snprintf_chk’ output between 4 and 67 bytes into a destination of size 64
54 | return __builtin___snprintf_chk (__s, __n, __USE_FORTIFY_LEVEL - 1,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
55 | __glibc_objsize (__s), __fmt,
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
56 | __va_arg_pack ());
| ~~~~~~~~~~~~~~~~~
In file included from config_parser.c:8:
config_parser.c: In function ‘parse_debug_categories’:
../lib/debug_config.h:42:36: warning: conversion from ‘long unsigned int’ to ‘uint32_t’ {aka ‘unsigned int’} changes value from ‘18446744073709551615’ to ‘4294967295’ [-Woverflow]
42 | #define DEBUG_CATEGORY_ALL (~((debug_category_t)0))
| ^
config_parser.c:38:29: note: in expansion of macro ‘DEBUG_CATEGORY_ALL’
38 | if (!value_copy) return DEBUG_CATEGORY_ALL; // Default to all on error
| ^~~~~~~~~~~~~~~~~~
../lib/debug_config.h:42:36: warning: conversion from ‘long unsigned int’ to ‘uint32_t’ {aka ‘unsigned int’} changes value from ‘18446744073709551615’ to ‘4294967295’ [-Woverflow]
42 | #define DEBUG_CATEGORY_ALL (~((debug_category_t)0))
| ^
config_parser.c:66:27: note: in expansion of macro ‘DEBUG_CATEGORY_ALL’
66 | categories |= DEBUG_CATEGORY_ALL;
| ^~~~~~~~~~~~~~~~~~
etcp.c: In function ‘etcp_connection_create’:
etcp.c:99:43: warning: passing argument 2 of ‘queue_set_callback’ from incompatible pointer type [-Wincompatible-pointer-types]
99 | queue_set_callback(etcp->input_queue, input_queue_cb, etcp);
| ^~~~~~~~~~~~~~
| |
| void (*)(struct ll_queue *, void *)
In file included from etcp.h:7,
from etcp.c:3:
../lib/ll_queue.h:83:63: note: expected ‘queue_callback_fn’ {aka ‘void (*)(struct ll_queue *, void *, void *)’} but argument is of type ‘void (*)(struct ll_queue *, void *)’
83 | void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
| ~~~~~~~~~~~~~~~~~~^~~~~~
etcp.c:100:44: warning: passing argument 2 of ‘queue_set_callback’ from incompatible pointer type [-Wincompatible-pointer-types]
100 | queue_set_callback(etcp->input_send_q, input_send_q_cb, etcp);
| ^~~~~~~~~~~~~~~
| |
| void (*)(struct ll_queue *, void *)
../lib/ll_queue.h:83:63: note: expected ‘queue_callback_fn’ {aka ‘void (*)(struct ll_queue *, void *, void *)’} but argument is of type ‘void (*)(struct ll_queue *, void *)’
83 | void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
| ~~~~~~~~~~~~~~~~~~^~~~~~
pkt_normalizer.c: In function ‘pkt_normalizer_send_service’:
pkt_normalizer.c:347:22: warning: initialization of ‘uint8_t *’ {aka ‘unsigned char *’} from incompatible pointer type ‘struct ll_entry *’ [-Wincompatible-pointer-types]
347 | uint8_t* d = (entry);
| ^
test_ll_queue.c:20: warning: "DEBUG_CATEGORY_LL_QUEUE" redefined
20 | #define DEBUG_CATEGORY_LL_QUEUE 1
|
In file included from test_ll_queue.c:16:
../lib/debug_config.h:32: note: this is the location of the previous definition
32 | #define DEBUG_CATEGORY_LL_QUEUE ((debug_category_t)1 << 1) // ll_queue module
|
test_ll_queue.c: In function ‘test_memory_pool_integration’:
test_ll_queue.c:670:21: error: ‘entry_data3’ undeclared (first use in this function)
670 | queue_data_free(entry_data3);
| ^~~~~~~~~~~
test_ll_queue.c:670:21: note: each undeclared identifier is reported only once for each function it appears in
test_ll_queue.c: In function ‘test_edge_cases’:
test_ll_queue.c:719:21: error: ‘entry_retrieved’ undeclared (first use in this function)
719 | queue_data_free(entry_retrieved);
| ^~~~~~~~~~~~~~~
test_ll_queue.c: In function ‘test_memory_pool_vs_malloc_performance’:
test_ll_queue.c:1005:31: error: ‘entry_data’ undeclared (first use in this function)
1005 | queue_data_put(q, entry_data, data->id);
| ^~~~~~~~~~
make[2]: *** [Makefile:1086: test_ll_queue-test_ll_queue.o] Ошибка 1
make[1]: *** [Makefile:1494: check-am] Ошибка 2
make: *** [Makefile:375: check-recursive] Ошибка 1
test_ll_queue.c:20: warning: "DEBUG_CATEGORY_LL_QUEUE" redefined
20 | #define DEBUG_CATEGORY_LL_QUEUE 1
|
In file included from test_ll_queue.c:16:
../lib/debug_config.h:32: note: this is the location of the previous definition
32 | #define DEBUG_CATEGORY_LL_QUEUE ((debug_category_t)1 << 1) // ll_queue module
|
test_ll_queue.c: In function ‘test_memory_pool_integration’:
test_ll_queue.c:670:21: error: ‘entry_data3’ undeclared (first use in this function)
670 | queue_data_free(entry_data3);
| ^~~~~~~~~~~
test_ll_queue.c:670:21: note: each undeclared identifier is reported only once for each function it appears in
test_ll_queue.c: In function ‘test_edge_cases’:
test_ll_queue.c:719:21: error: ‘entry_retrieved’ undeclared (first use in this function)
719 | queue_data_free(entry_retrieved);
| ^~~~~~~~~~~~~~~
test_ll_queue.c: In function ‘test_memory_pool_vs_malloc_performance’:
test_ll_queue.c:1005:31: error: ‘entry_data’ undeclared (first use in this function)
1005 | queue_data_put(q, entry_data, data->id);
| ^~~~~~~~~~
make[2]: *** [Makefile:1086: test_ll_queue-test_ll_queue.o] Ошибка 1
make[1]: *** [Makefile:1494: check-am] Ошибка 2
make: *** [Makefile:375: check-recursive] Ошибка 1
test_ll_queue.c:20: warning: "DEBUG_CATEGORY_LL_QUEUE" redefined
20 | #define DEBUG_CATEGORY_LL_QUEUE 1
|
In file included from test_ll_queue.c:16:
../lib/debug_config.h:32: note: this is the location of the previous definition
32 | #define DEBUG_CATEGORY_LL_QUEUE ((debug_category_t)1 << 1) // ll_queue module
|
test_ll_queue.c: In function ‘test_memory_pool_integration’:
test_ll_queue.c:670:21: error: ‘entry_data3’ undeclared (first use in this function)
670 | queue_data_free(entry_data3);
| ^~~~~~~~~~~
test_ll_queue.c:670:21: note: each undeclared identifier is reported only once for each function it appears in
test_ll_queue.c: In function ‘test_edge_cases’:
test_ll_queue.c:719:21: error: ‘entry_retrieved’ undeclared (first use in this function)
719 | queue_data_free(entry_retrieved);
| ^~~~~~~~~~~~~~~
test_ll_queue.c: In function ‘test_memory_pool_vs_malloc_performance’:
test_ll_queue.c:1005:31: error: ‘entry_data’ undeclared (first use in this function)
1005 | queue_data_put(q, entry_data, data->id);
| ^~~~~~~~~~
make[2]: *** [Makefile:1086: test_ll_queue-test_ll_queue.o] Ошибка 1
make[1]: *** [Makefile:1494: check-am] Ошибка 2
make: *** [Makefile:375: check-recursive] Ошибка 1
ar: модификатор «u» игнорируется, так как по умолчанию используется «D» (смотрите «U»)
utun_instance.c: In function ‘utun_instance_create’:
utun_instance.c:109:53: warning: ‘/32’ directive output may be truncated writing 3 bytes into a region of size between 1 and 64 [-Wformat-truncation=]
109 | snprintf(tun_ip_str, sizeof(tun_ip_str), "%s/32", ip_buffer);
| ^~~
In file included from /usr/include/stdio.h:980,
from utun_instance.h:6,
from utun_instance.c:2:
In function ‘snprintf’,
inlined from ‘utun_instance_create’ at utun_instance.c:109:9:
/usr/include/x86_64-linux-gnu/bits/stdio2.h:54:10: note: ‘__builtin___snprintf_chk’ output between 4 and 67 bytes into a destination of size 64
54 | return __builtin___snprintf_chk (__s, __n, __USE_FORTIFY_LEVEL - 1,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
55 | __glibc_objsize (__s), __fmt,
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
56 | __va_arg_pack ());
| ~~~~~~~~~~~~~~~~~
In file included from config_parser.c:8:
config_parser.c: In function ‘parse_debug_categories’:
../lib/debug_config.h:42:36: warning: conversion from ‘long unsigned int’ to ‘uint32_t’ {aka ‘unsigned int’} changes value from ‘18446744073709551615’ to ‘4294967295’ [-Woverflow]
42 | #define DEBUG_CATEGORY_ALL (~((debug_category_t)0))
| ^
config_parser.c:38:29: note: in expansion of macro ‘DEBUG_CATEGORY_ALL’
38 | if (!value_copy) return DEBUG_CATEGORY_ALL; // Default to all on error
| ^~~~~~~~~~~~~~~~~~
../lib/debug_config.h:42:36: warning: conversion from ‘long unsigned int’ to ‘uint32_t’ {aka ‘unsigned int’} changes value from ‘18446744073709551615’ to ‘4294967295’ [-Woverflow]
42 | #define DEBUG_CATEGORY_ALL (~((debug_category_t)0))
| ^
config_parser.c:66:27: note: in expansion of macro ‘DEBUG_CATEGORY_ALL’
66 | categories |= DEBUG_CATEGORY_ALL;
| ^~~~~~~~~~~~~~~~~~
etcp.c: In function ‘etcp_connection_create’:
etcp.c:99:43: warning: passing argument 2 of ‘queue_set_callback’ from incompatible pointer type [-Wincompatible-pointer-types]
99 | queue_set_callback(etcp->input_queue, input_queue_cb, etcp);
| ^~~~~~~~~~~~~~
| |
| void (*)(struct ll_queue *, void *)
In file included from etcp.h:7,
from etcp.c:3:
../lib/ll_queue.h:83:63: note: expected ‘queue_callback_fn’ {aka ‘void (*)(struct ll_queue *, void *, void *)’} but argument is of type ‘void (*)(struct ll_queue *, void *)’
83 | void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
| ~~~~~~~~~~~~~~~~~~^~~~~~
etcp.c:100:44: warning: passing argument 2 of ‘queue_set_callback’ from incompatible pointer type [-Wincompatible-pointer-types]
100 | queue_set_callback(etcp->input_send_q, input_send_q_cb, etcp);
| ^~~~~~~~~~~~~~~
| |
| void (*)(struct ll_queue *, void *)
../lib/ll_queue.h:83:63: note: expected ‘queue_callback_fn’ {aka ‘void (*)(struct ll_queue *, void *, void *)’} but argument is of type ‘void (*)(struct ll_queue *, void *)’
83 | void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
| ~~~~~~~~~~~~~~~~~~^~~~~~
pkt_normalizer.c: In function ‘pkt_normalizer_send_service’:
pkt_normalizer.c:347:22: warning: initialization of ‘uint8_t *’ {aka ‘unsigned char *’} from incompatible pointer type ‘struct ll_entry *’ [-Wincompatible-pointer-types]
347 | uint8_t* d = (entry);
| ^
test_ll_queue.c:20: warning: "DEBUG_CATEGORY_LL_QUEUE" redefined
20 | #define DEBUG_CATEGORY_LL_QUEUE 1
|
In file included from test_ll_queue.c:16:
../lib/debug_config.h:32: note: this is the location of the previous definition
32 | #define DEBUG_CATEGORY_LL_QUEUE ((debug_category_t)1 << 1) // ll_queue module
|
test_ll_queue.c: In function ‘test_memory_pool_integration’:
test_ll_queue.c:670:21: error: ‘entry_data3’ undeclared (first use in this function)
670 | queue_data_free(entry_data3);
| ^~~~~~~~~~~
test_ll_queue.c:670:21: note: each undeclared identifier is reported only once for each function it appears in
test_ll_queue.c: In function ‘test_edge_cases’:
test_ll_queue.c:719:21: error: ‘entry_retrieved’ undeclared (first use in this function)
719 | queue_data_free(entry_retrieved);
| ^~~~~~~~~~~~~~~
test_ll_queue.c: In function ‘test_memory_pool_vs_malloc_performance’:
test_ll_queue.c:1005:31: error: ‘entry_data’ undeclared (first use in this function)
1005 | queue_data_put(q, entry_data, data->id);
| ^~~~~~~~~~
make[2]: *** [Makefile:1086: test_ll_queue-test_ll_queue.o] Ошибка 1
make[1]: *** [Makefile:1494: check-am] Ошибка 2
make: *** [Makefile:375: check-recursive] Ошибка 1
test_ll_queue.c:20: warning: "DEBUG_CATEGORY_LL_QUEUE" redefined
20 | #define DEBUG_CATEGORY_LL_QUEUE 1
|
In file included from test_ll_queue.c:16:
../lib/debug_config.h:32: note: this is the location of the previous definition
32 | #define DEBUG_CATEGORY_LL_QUEUE ((debug_category_t)1 << 1) // ll_queue module
|
test_u_async_comprehensive.c: In function ‘test_concurrent_operations’:
test_u_async_comprehensive.c:428:13: warning: ignoring return value of ‘write’ declared with attribute ‘warn_unused_result’ [-Wunused-result]
428 | write(sockets[0], &data, 1);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from test_debug_categories.c:8:
test_debug_categories.c: In function ‘main’:
../lib/debug_config.h:42:36: warning: overflow in conversion from ‘long unsigned int’ to ‘int’ changes value from ‘18446744073709551615’ to ‘-1’ [-Woverflow]
42 | #define DEBUG_CATEGORY_ALL (~((debug_category_t)0))
| ^
test_debug_categories.c:64:32: note: in expansion of macro ‘DEBUG_CATEGORY_ALL’
64 | debug_categories = DEBUG_CATEGORY_ALL;
| ^~~~~~~~~~~~~~~~~~
../build-aux/test-driver: строка 112: 1203433 Ошибка сегментирования (образ памяти сброшен на диск) "$@" >> "$log_file" 2>&1
make[3]: *** [Makefile:1223: test-suite.log] Ошибка 1
make[2]: *** [Makefile:1331: check-TESTS] Ошибка 2
make[1]: *** [Makefile:1495: check-am] Ошибка 2
make: *** [Makefile:375: check-recursive] Ошибка 1
test_ll_queue.c:20: warning: "DEBUG_CATEGORY_LL_QUEUE" redefined
20 | #define DEBUG_CATEGORY_LL_QUEUE 1
|
In file included from test_ll_queue.c:16:
../lib/debug_config.h:32: note: this is the location of the previous definition
32 | #define DEBUG_CATEGORY_LL_QUEUE ((debug_category_t)1 << 1) // ll_queue module
|
../build-aux/test-driver: строка 112: 1203954 Ошибка сегментирования (образ памяти сброшен на диск) "$@" >> "$log_file" 2>&1
make[3]: *** [Makefile:1223: test-suite.log] Ошибка 1
make[2]: *** [Makefile:1331: check-TESTS] Ошибка 2
make[1]: *** [Makefile:1495: check-am] Ошибка 2
make: *** [Makefile:375: check-recursive] Ошибка 1
make: *** Нет правила для сборки цели «test_ll_queue». Останов.
make: *** Нет правила для сборки цели «test_ll_queue». Останов.
make: *** Нет правила для сборки цели «test_ll_queue». Останов.
/usr/bin/ld: невозможно найти ../lib/libuasync.a: Нет такого файла или каталога
collect2: error: ld returned 1 exit status
/usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/Scrt1.o: в функции «_start»:
(.text+0x1b): undefined reference to `main'
collect2: error: ld returned 1 exit status
timeout: не удалось выполнить команду «./test_ll_queue»: Нет такого файла или каталога
timeout: не удалось выполнить команду «./test_ll_queue»: Нет такого файла или каталога
timeout: не удалось выполнить команду «./test_ll_queue»: Нет такого файла или каталога
/usr/bin/ld: невозможно найти ../lib/libuasync.a: Нет такого файла или каталога
collect2: error: ld returned 1 exit status

2
AGENTS.md

@ -214,6 +214,8 @@ Crypto: Fixed CCM nonce size to 13 bytes, all crypto tests passing
- Когда пишешь код всегда загружай в контекст и мысленно разберись как работают все функции/струтуры, назначение переменных которые используешь.
- Для отладки не printf а DEBUG_*
- перед сборкой всегда make clean
- прежде чем вносить правки хорошо разберись как должно работать, просмотри все нужные функции полностью, нужно целостное понимание.
- всегда думай можно ли написать проще. Проверяй дубликаты и что уже есть, продумай логику до тех пор пока не будет полного понимания
Действия при поиске бага:
1. создать комит или бэкап всего что меняешь

72
lib/debug_config.c

@ -1,6 +1,5 @@
/**
* Debug configuration implementation
* Runtime debug configuration system for flexible debug output control
* Debug module
*/
#include "debug_config.h"
@ -13,6 +12,75 @@
#include <time.h>
#include <strings.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
void log_dump(const char* prefix, const uint8_t* data, size_t len) {
// Build packet data as hex string for single-line output
char hex_buf[513]; // 256 bytes * 2 chars + 1 for null terminator
size_t hex_len = 0;
size_t show_len = (len > 128) ? 128 : len; // Show max 128 bytes
for (size_t i = 0; i < show_len && hex_len < 512 - 3; i++) {
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, "%02x", data[i]);
if (i < show_len - 1 && (i + 1) % 32 == 0) { // Add space every 32 bytes
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, " ");
}
}
if (len > 128) {
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, "...");
}
// Single-line debug output with packet info
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "%s: len=%zu hex=%s", prefix, len, hex_buf);
// Additional debug info for first few bytes
if (len >= 2) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "%s: first_bytes=%02x%02x last_bytes=%02x%02x",
prefix, data[0], data[1], data[len-2], data[len-1]);
}
}
size_t addr_to_string(const struct sockaddr_storage *addr, char *buf, size_t buflen) {
if (!buf || buflen == 0)
return 0;
buf[0] = '\0'; // всегда валидная строка
char ip[INET6_ADDRSTRLEN];
uint16_t port;
int n;
if (addr->ss_family == AF_INET) {
const struct sockaddr_in *a = (const struct sockaddr_in *)addr;
inet_ntop(AF_INET, &a->sin_addr, ip, sizeof(ip));
port = ntohs(a->sin_port);
n = snprintf(buf, buflen, "%s:%u", ip, port);
} else if (addr->ss_family == AF_INET6) {
const struct sockaddr_in6 *a = (const struct sockaddr_in6 *)addr;
inet_ntop(AF_INET6, &a->sin6_addr, ip, sizeof(ip));
port = ntohs(a->sin6_port);
n = snprintf(buf, buflen, "[%s]:%u", ip, port);
} else {
return 0;
}
// snprintf гарантирует '\0', но на всякий случай:
buf[buflen - 1] = '\0';
// Возвращаем фактическую длину (как snprintf)
return (n < 0) ? 0 : (size_t)n;
}
/* Get category by name */
static debug_category_t get_category_by_name(const char* name) {
struct {

435
lib/debug_config.c1

@ -1,435 +0,0 @@
/**
* Debug configuration implementation
* Runtime debug configuration system for flexible debug output control
*/
#include "debug_config.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <stdarg.h>
#include <stdio.h>
#include <time.h>
#include <strings.h>
/* Rate limiting */
static size_t output_count = 0;
static time_t last_output_time = 0;
/* ANSI color codes */
static const char* color_red = "\033[31m";
static const char* color_yellow = "\033[33m";
static const char* color_green = "\033[32m";
static const char* color_blue = "\033[34m";
static const char* color_magenta = "\033[35m";
static const char* color_cyan = "\033[36m";
static const char* color_reset = "\033[0m";
/* Get category by name */
static debug_category_t get_category_by_name(const char* name) {
struct {
const char* n;
debug_category_t c;
} map[] = {
{"none", DEBUG_CATEGORY_NONE},
{"uasync", DEBUG_CATEGORY_UASYNC},
{"ll_queue", DEBUG_CATEGORY_LL_QUEUE},
{"connection", DEBUG_CATEGORY_CONNECTION},
{"etcp", DEBUG_CATEGORY_ETCP},
{"crypto", DEBUG_CATEGORY_CRYPTO},
{"memory", DEBUG_CATEGORY_MEMORY},
{"timing", DEBUG_CATEGORY_TIMING},
{"config", DEBUG_CATEGORY_CONFIG},
{"tun", DEBUG_CATEGORY_TUN},
{"routing", DEBUG_CATEGORY_ROUTING},
{"timers", DEBUG_CATEGORY_TIMERS},
{"all", DEBUG_CATEGORY_ALL},
{NULL, 0}
};
for (int i = 0; map[i].n; i++) {
if (strcasecmp(map[i].n, name) == 0) {
return map[i].c;
}
}
return DEBUG_CATEGORY_NONE;
}
/* Get level by name */
static debug_level_t get_level_by_name(const char* name) {
struct {
const char* n;
debug_level_t l;
} map[] = {
{"none", DEBUG_LEVEL_NONE},
{"error", DEBUG_LEVEL_ERROR},
{"warn", DEBUG_LEVEL_WARN},
{"info", DEBUG_LEVEL_INFO},
{"debug", DEBUG_LEVEL_DEBUG},
{"trace", DEBUG_LEVEL_TRACE},
{NULL, 0}
};
for (int i = 0; map[i].n; i++) {
if (strcasecmp(map[i].n, name) == 0) {
return map[i].l;
}
}
return DEBUG_LEVEL_NONE;
}
/* Global debug configuration */
debug_config_t g_debug_config;
FILE* debug_output_file = NULL;
/* Forward declaration for global_config structure */
struct global_config {
char log_file[256];
char debug_level[32];
uint32_t debug_categories;
int enable_timestamp;
int enable_function_names;
int enable_file_lines;
int enable_colors;
};
/* Apply debug settings from configuration if not overridden by CLI */
void debug_apply_config(const struct global_config *config, int cli_override_level, int cli_override_categories) {
if (!config) return;
// Apply log file if specified and not overridden
if (config->log_file[0] != '\0') {
debug_set_output_file(config->log_file);
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Set log file to: %s", config->log_file);
}
// Apply debug level from config if not overridden by CLI
if (!cli_override_level && config->debug_level[0] != '\0') {
debug_level_t level = DEBUG_LEVEL_INFO; // default
if (strcasecmp(config->debug_level, "error") == 0) level = DEBUG_LEVEL_ERROR;
else if (strcasecmp(config->debug_level, "warn") == 0) level = DEBUG_LEVEL_WARN;
else if (strcasecmp(config->debug_level, "info") == 0) level = DEBUG_LEVEL_INFO;
else if (strcasecmp(config->debug_level, "debug") == 0) level = DEBUG_LEVEL_DEBUG;
else if (strcasecmp(config->debug_level, "trace") == 0) level = DEBUG_LEVEL_TRACE;
debug_set_level(level);
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Set debug level from config: %s", config->debug_level);
}
// Apply debug categories from config if not overridden by CLI
if (!cli_override_categories && config->debug_categories != 0) {
debug_set_categories(config->debug_categories);
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Set debug categories from config: 0x%X", config->debug_categories);
}
// Apply output options from config
debug_enable_timestamp(config->enable_timestamp);
debug_enable_function_name(config->enable_function_names);
debug_enable_file_line(config->enable_file_lines);
debug_enable_color(config->enable_colors);
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Applied debug configuration from file");
}
/* Initialize debug system with default settings */
void debug_config_init(void) {
g_debug_config.default_level = DEBUG_LEVEL_ERROR;
g_debug_config.categories = DEBUG_CATEGORY_ALL;
memset(g_debug_config.category_levels, 0, sizeof(g_debug_config.category_levels));
g_debug_config.timestamp_enabled = 1;
g_debug_config.function_name_enabled = 1;
g_debug_config.file_line_enabled = 1;
g_debug_config.color_enabled = 1;
g_debug_config.max_output_per_second = 0;
g_debug_config.output_file = NULL;
if (debug_output_file && debug_output_file != stdout && debug_output_file != stderr) {
fclose(debug_output_file);
}
debug_output_file = stdout;
output_count = 0;
last_output_time = 0;
}
/* Set debug level */
void debug_set_level(debug_level_t level) {
g_debug_config.default_level = level;
}
/* Enable/disable specific categories */
void debug_enable_category(debug_category_t category) {
g_debug_config.categories |= category;
}
void debug_disable_category(debug_category_t category) {
g_debug_config.categories &= ~category;
}
void debug_set_categories(uint32_t categories) {
g_debug_config.categories = categories;
}
/* Configure output options */
void debug_enable_timestamp(int enable) {
g_debug_config.timestamp_enabled = enable;
}
void debug_enable_function_name(int enable) {
g_debug_config.function_name_enabled = enable;
}
void debug_enable_file_line(int enable) {
g_debug_config.file_line_enabled = enable;
}
void debug_enable_color(int enable) {
g_debug_config.color_enabled = enable;
}
void debug_set_rate_limit(size_t max_per_second) {
g_debug_config.max_output_per_second = max_per_second;
}
void debug_set_output_file(const char* file_path) {
if (file_path == NULL) {
if (debug_output_file && debug_output_file != stdout && debug_output_file != stderr) {
fclose(debug_output_file);
}
debug_output_file = stdout;
g_debug_config.output_file = NULL;
} else {
FILE* new_file = fopen(file_path, "a");
if (new_file) {
if (debug_output_file && debug_output_file != stdout && debug_output_file != stderr) {
fclose(debug_output_file);
}
debug_output_file = new_file;
g_debug_config.output_file = file_path;
}
}
}
/* Check if debug output should be shown for given level and category */
int debug_should_output(debug_level_t level, debug_category_t category) {
/* Check if category is enabled */
if (!(g_debug_config.categories & category)) {
return 0;
}
/* Check if level is sufficient */
debug_level_t eff_level = debug_get_effective_level(category);
if (level > eff_level) {
return 0;
}
/* Rate limiting check */
if (g_debug_config.max_output_per_second > 0) {
time_t current_time = time(NULL);
if (current_time != last_output_time) {
output_count = 0;
last_output_time = current_time;
}
if (output_count >= g_debug_config.max_output_per_second) {
return 0;
}
output_count++;
}
return 1;
}
/* Get current debug level for a category */
debug_level_t debug_get_effective_level(debug_category_t category) {
if (category == DEBUG_CATEGORY_ALL || category == DEBUG_CATEGORY_NONE) {
return g_debug_config.default_level;
}
/* Assume single category bit */
uint32_t cat = category;
if (__builtin_popcount(cat) != 1) {
return DEBUG_LEVEL_NONE; /* Not supported for multiple */
}
int index = __builtin_ctz(cat);
if (index >= NUM_DEBUG_CATEGORIES) {
return DEBUG_LEVEL_NONE;
}
debug_level_t lev = g_debug_config.category_levels[index];
return (lev == DEBUG_LEVEL_NONE) ? g_debug_config.default_level : lev;
}
/* Get color for debug level */
static const char* get_level_color(debug_level_t level) {
if (!g_debug_config.color_enabled) {
return "";
}
switch (level) {
case DEBUG_LEVEL_ERROR: return color_red;
case DEBUG_LEVEL_WARN: return color_yellow;
case DEBUG_LEVEL_INFO: return color_green;
case DEBUG_LEVEL_DEBUG: return color_blue;
case DEBUG_LEVEL_TRACE: return color_magenta;
default: return "";
}
}
/* Get level name */
static const char* get_level_name(debug_level_t level) {
switch (level) {
case DEBUG_LEVEL_ERROR: return "ERROR";
case DEBUG_LEVEL_WARN: return "WARN";
case DEBUG_LEVEL_INFO: return "INFO";
case DEBUG_LEVEL_DEBUG: return "DEBUG";
case DEBUG_LEVEL_TRACE: return "TRACE";
default: return "UNKNOWN";
}
}
/* Format and output debug message */
void debug_output(debug_level_t level, debug_category_t category,
const char* function, const char* file, int line,
const char* format, ...) {
#define BUFFER_SIZE 4096
char buffer[BUFFER_SIZE];
int offset = 0;
size_t remaining = BUFFER_SIZE;
va_list args;
va_start(args, format);
FILE* output = debug_output_file ? debug_output_file : stdout;
/* Add timestamp if enabled */
if (g_debug_config.timestamp_enabled) {
time_t now = time(NULL);
struct tm* tm_info = localtime(&now);
char time_str[32];
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
offset += snprintf(buffer + offset, remaining, "[%s] ", time_str);
remaining = BUFFER_SIZE - offset;
}
/* Add level and color */
const char* color = get_level_color(level);
const char* level_name = get_level_name(level);
if (g_debug_config.color_enabled) {
offset += snprintf(buffer + offset, remaining, "%s[%s]%s ", color, level_name, color_reset);
} else {
offset += snprintf(buffer + offset, remaining, "[%s] ", level_name);
}
remaining = BUFFER_SIZE - offset;
/* Add category */
offset += snprintf(buffer + offset, remaining, "[%d] ", category);
remaining = BUFFER_SIZE - offset;
/* Add function name if enabled */
if (g_debug_config.function_name_enabled && function) {
offset += snprintf(buffer + offset, remaining, "%s() ", function);
remaining = BUFFER_SIZE - offset;
}
/* Add file:line if enabled */
if (g_debug_config.file_line_enabled && file) {
offset += snprintf(buffer + offset, remaining, "(%s:%d) ", file, line);
remaining = BUFFER_SIZE - offset;
}
/* Add the actual message */
offset += vsnprintf(buffer + offset, remaining, format, args);
remaining = BUFFER_SIZE - offset;
/* Add newline */
if (remaining > 1) {
buffer[offset] = '\n';
buffer[offset + 1] = '\0';
} else {
buffer[BUFFER_SIZE - 2] = '\n';
buffer[BUFFER_SIZE - 1] = '\0';
}
va_end(args);
/* Output the entire line at once */
fprintf(output, "%s", buffer);
fflush(output);
#undef BUFFER_SIZE
}
/* Parse debug configuration from string */
int debug_parse_config(const char* config_string) {
if (!config_string || !*config_string) {
return 0;
}
char* str = strdup(config_string);
if (!str) {
return -1;
}
char* token = strtok(str, ",");
while (token) {
/* Trim leading spaces */
while (isspace(*token)) {
token++;
}
char* colon = strchr(token, ':');
if (!colon) {
free(str);
return -1;
}
*colon = '\0';
char* cat_name = token;
char* level_name = colon + 1;
/* Trim leading spaces in level_name */
while (isspace(*level_name)) {
level_name++;
}
/* Find category */
debug_category_t cat = get_category_by_name(cat_name);
if (cat == DEBUG_CATEGORY_NONE) {
free(str);
return -1;
}
/* Find level */
debug_level_t lev = get_level_by_name(level_name);
if (lev == DEBUG_LEVEL_NONE) {
free(str);
return -1;
}
if (cat == DEBUG_CATEGORY_ALL) {
g_debug_config.default_level = lev;
} else {
g_debug_config.categories |= cat;
int index = __builtin_ctz(cat);
if (index < NUM_DEBUG_CATEGORIES) {
g_debug_config.category_levels[index] = lev;
}
}
token = strtok(NULL, ",");
}
free(str);
return 0;
}
/* Apply debug settings from configuration string values */
void debug_apply_config_values(const char *log_file, const char *debug_level_str, uint32_t debug_categories,
int enable_timestamp, int enable_function_names, int enable_file_lines, int enable_colors,
int cli_override_level, int cli_override_categories) {
// This function would be implemented to apply individual config values
// For now, it's a placeholder that could be expanded based on specific needs
}

8
lib/debug_config.h

@ -6,6 +6,8 @@
#ifndef DEBUG_CONFIG_H
#define DEBUG_CONFIG_H
#include <sys/socket.h> // struct sockaddr_storage
#include <stddef.h> // size_t
#include <stdint.h>
#include <stddef.h>
#include <time.h>
@ -74,6 +76,12 @@ void debug_enable_function_name(int enable);
void debug_enable_file_line(int enable);
void debug_set_output_file(const char* file_path);
// вывод struct sockaddr_storage как текст
size_t addr_to_string(const struct sockaddr_storage *addr, char *buf, size_t buflen);
// hex dump в лог
void log_dump(const char* prefix, const uint8_t* data, size_t len);
#include <stdio.h>
extern FILE* debug_output_file;

137
lib/debug_config.h1

@ -1,137 +0,0 @@
/**
* Runtime debug configuration system
* Provides flexible control over debug output without recompilation
*/
#ifndef DEBUG_CONFIG_H
#define DEBUG_CONFIG_H
#include <stdint.h>
#include <stddef.h>
#include <time.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Number of categories (bits 0-10) */
#define NUM_DEBUG_CATEGORIES 11
/* Debug levels */
typedef enum {
DEBUG_LEVEL_NONE = 0, // No debug output
DEBUG_LEVEL_ERROR = 1, // Errors only
DEBUG_LEVEL_WARN = 2, // Warnings and errors
DEBUG_LEVEL_INFO = 3, // Info, warnings, errors
DEBUG_LEVEL_DEBUG = 4, // Full debug output
DEBUG_LEVEL_TRACE = 5 // Trace everything
} debug_level_t;
/* Debug categories - can be combined with bitwise OR */
typedef enum {
DEBUG_CATEGORY_NONE = 0,
DEBUG_CATEGORY_UASYNC = 1 << 0, // u_async module
DEBUG_CATEGORY_LL_QUEUE = 1 << 1, // ll_queue module
DEBUG_CATEGORY_CONNECTION = 1 << 2, // connection module
DEBUG_CATEGORY_ETCP = 1 << 3, // etcp module
DEBUG_CATEGORY_CRYPTO = 1 << 4, // crypto operations
DEBUG_CATEGORY_MEMORY = 1 << 5, // memory management
DEBUG_CATEGORY_TIMING = 1 << 6, // timing/performance
DEBUG_CATEGORY_CONFIG = 1 << 7, // configuration parsing
DEBUG_CATEGORY_TUN = 1 << 8, // TUN interface
DEBUG_CATEGORY_ROUTING = 1 << 9, // routing table
DEBUG_CATEGORY_TIMERS = 1 << 10, // timer management
DEBUG_CATEGORY_ALL = 0xFFFFFFFF
} debug_category_t;
/* Debug configuration structure */
typedef struct {
debug_level_t default_level; // Default debug level
uint32_t categories; // Enabled categories (bitmask)
debug_level_t category_levels[NUM_DEBUG_CATEGORIES]; // Per-category levels (NONE means use default)
int timestamp_enabled; // Include timestamps in output
int function_name_enabled; // Include function names
int file_line_enabled; // Include file:line info
int color_enabled; // Use ANSI colors (if terminal supports)
size_t max_output_per_second; // Rate limiting (0 = unlimited)
const char* output_file; // NULL = stdout, otherwise file path
} debug_config_t;
/* Global debug configuration */
extern debug_config_t g_debug_config;
/* Initialize debug system with default settings */
void debug_config_init(void);
/* Set debug level */
void debug_set_level(debug_level_t level);
/* Enable/disable specific categories */
void debug_enable_category(debug_category_t category);
void debug_disable_category(debug_category_t category);
void debug_set_categories(uint32_t categories);
/* Configure output options */
void debug_enable_timestamp(int enable);
void debug_enable_function_name(int enable);
void debug_enable_file_line(int enable);
void debug_enable_color(int enable);
void debug_set_rate_limit(size_t max_per_second);
void debug_set_output_file(const char* file_path);
/* Parse debug configuration from string (e.g., "uasync:debug,ll_queue:info") */
int debug_parse_config(const char* config_string);
#include <stdio.h>
extern FILE* debug_output_file;
/* Check if debug output should be shown for given level and category */
int debug_should_output(debug_level_t level, debug_category_t category);
/* Get current debug level for a category */
debug_level_t debug_get_effective_level(debug_category_t category);
/* Format and output debug message (internal use by macros) */
void debug_output(debug_level_t level, debug_category_t category,
const char* function, const char* file, int line,
const char* format, ...);
/* Convenience macros for debug output */
#define DEBUG_ERROR(category, fmt, ...) \
do { if (debug_should_output(DEBUG_LEVEL_ERROR, category)) { \
debug_output(DEBUG_LEVEL_ERROR, category, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \
} } while(0)
#define DEBUG_WARN(category, fmt, ...) \
do { if (debug_should_output(DEBUG_LEVEL_WARN, category)) { \
debug_output(DEBUG_LEVEL_WARN, category, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \
} } while(0)
#define DEBUG_INFO(category, fmt, ...) \
do { if (debug_should_output(DEBUG_LEVEL_INFO, category)) { \
debug_output(DEBUG_LEVEL_INFO, category, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \
} } while(0)
#define DEBUG_DEBUG(category, fmt, ...) \
do { if (debug_should_output(DEBUG_LEVEL_DEBUG, category)) { \
debug_output(DEBUG_LEVEL_DEBUG, category, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \
} } while(0)
#define DEBUG_TRACE(category, fmt, ...) \
do { if (debug_should_output(DEBUG_LEVEL_TRACE, category)) { \
debug_output(DEBUG_LEVEL_TRACE, category, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \
} } while(0)
/* Backward compatibility - default to ERROR level if not specified */
#define DEBUG_OUTPUT(fmt, ...) DEBUG_ERROR(DEBUG_CATEGORY_ALL, fmt, ##__VA_ARGS__)
/* Apply debug settings from configuration string values */
void debug_apply_config_values(const char *log_file, const char *debug_level_str, uint32_t debug_categories,
int enable_timestamp, int enable_function_names, int enable_file_lines, int enable_colors,
int cli_override_level, int cli_override_categories);
#ifdef __cplusplus
}
#endif
#endif // DEBUG_CONFIG_H

22
lib/ll_queue.c

@ -86,8 +86,7 @@ static void queue_resume_timeout_cb(void* arg) {
// Вызвать коллбэк если есть элементы и коллбэки разрешены
if (q->head && !q->callback_suspended && q->callback) {
void* data = (void*)(q->head + xxx);
q->callback(q, data, q->callback_arg);
q->callback(q, q->callback_arg);
}
}
@ -229,15 +228,17 @@ int queue_data_put(struct ll_queue* q, void* data, uint32_t id) {
q->count++;
q->total_bytes += entry->size;
// ВАЖНО: НЕ увеличиваем ref_count - элемент просто находится в очереди
size_t send_q_bytes = queue_total_bytes(q);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "qqqq check total bytes: new_q_len=%d element_size:%d", send_q_bytes, entry->size);
add_to_hash(q, entry);
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_data_put: added entry %p (id=%u), count=%d",
entry, id, q->count);
// DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_data_put: added entry %p (id=%u), count=%d", entry, id, q->count);
// Если очередь была пуста и коллбэки разрешены - вызвать коллбэк
if (q->count == 1 && !q->callback_suspended && q->callback) {
q->callback(q, data, q->callback_arg);
q->callback(q, q->callback_arg);
}
// Проверить ожидающие коллбэки
@ -274,12 +275,11 @@ int queue_data_put_first(struct ll_queue* q, void* data, uint32_t id) {
add_to_hash(q, entry);
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_data_put_first: added entry %p (id=%u), count=%d",
entry, id, q->count);
// DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_data_put_first: added entry %p (id=%u), count=%d", entry, id, q->count);
// Если очередь была пуста и коллбэки разрешены - вызвать коллбэк
if (q->count == 1 && !q->callback_suspended && q->callback) {
q->callback(q, data, q->callback_arg);
q->callback(q, q->callback_arg);
}
// Проверить ожидающие коллбэки
@ -307,8 +307,7 @@ void* queue_data_get(struct ll_queue* q) {
// ВАЖНО: НЕ уменьшаем ref_count - просто удаляем из очереди
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_data_get: got entry %p (id=%u), count=%d",
entry, entry->id, q->count);
// DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_data_get: got entry %p (id=%u), count=%d", entry, entry->id, q->count);
// Приостановить коллбэки для предотвращения рекурсии
q->callback_suspended = 1;
@ -399,8 +398,7 @@ int queue_remove_data(struct ll_queue* q, void* data) {
remove_from_hash(q, entry);
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_remove_data: removed entry %p (id=%u), count=%d",
entry, entry->id, q->count);
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_remove_data: removed entry %p (id=%u), count=%d", entry, entry->id, q->count);
return 0;
}

2
lib/ll_queue.h

@ -16,7 +16,7 @@ struct UASYNC;
// Автозабор элемента из очереди. вызывается когда в очереди что-то есть и коллбэк не занят обработкой.
// Параметры: указатель на очередь, указатель на структуру элемента (struct ll_entry*), пользовательский аргумент
// Когда коллбэк закончит обрабатывать элемент он должен вызвать queue_resume_callback - сообщить о готовности получить следующий элемент (приём очередным вызовом коллбэка в следующем цикле mainloop).
typedef void (*queue_callback_fn)(struct ll_queue* q, void* data, void* arg);
typedef void (*queue_callback_fn)(struct ll_queue* q, void* arg);
// Структура элемента - переменный размер, данные расположены сразу после структуры
struct ll_entry {

131
lib/timeout_heap.c1

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

170
lib/timeout_heap.c2

@ -1,170 +0,0 @@
// Updated timeout_heap.c
#include "timeout_heap.h"
#include "debug_config.h"
#include <stdlib.h>
#include <stdio.h> // For potential error printing, optional
#include <limits.h> // For SIZE_MAX
// Helper macros for 1-based indices
#define PARENT(i) ((i) / 2)
#define LEFT_CHILD(i) (2 * (i))
#define RIGHT_CHILD(i) (2 * (i) + 1)
// Forward declaration of timeout_node (from u_async.h, but included here for type safety)
struct timeout_node;
// Private helpers
static void update_index(void *data, size_t idx) {
if (data) {
((struct timeout_node *)data)->heap_index = idx;
}
}
static void bubble_up(TimeoutHeap *h, size_t i) {
// i is 1-based
while (i > 1 && h->heap[PARENT(i) - 1].expiration > h->heap[i - 1].expiration) {
// Swap with parent
TimeoutEntry temp = h->heap[PARENT(i) - 1];
h->heap[PARENT(i) - 1] = h->heap[i - 1];
h->heap[i - 1] = temp;
// Update indices
update_index(h->heap[PARENT(i) - 1].data, PARENT(i) - 1);
update_index(h->heap[i - 1].data, i - 1);
i = PARENT(i);
}
}
static void heapify_down(TimeoutHeap *h, size_t i) {
// i is 1-based
while (1) {
size_t smallest = i;
size_t left = LEFT_CHILD(i);
size_t right = RIGHT_CHILD(i);
if (left <= h->size && h->heap[left - 1].expiration < h->heap[smallest - 1].expiration) {
smallest = left;
}
if (right <= h->size && h->heap[right - 1].expiration < h->heap[smallest - 1].expiration) {
smallest = right;
}
if (smallest == i) break;
// Swap
TimeoutEntry temp = h->heap[smallest - 1];
h->heap[smallest - 1] = h->heap[i - 1];
h->heap[i - 1] = temp;
// Update indices
update_index(h->heap[smallest - 1].data, smallest - 1);
update_index(h->heap[i - 1].data, i - 1);
i = smallest;
}
}
TimeoutHeap *timeout_heap_create(size_t initial_capacity) {
TimeoutHeap *h = malloc(sizeof(TimeoutHeap));
if (!h) return NULL;
h->heap = malloc(sizeof(TimeoutEntry) * initial_capacity);
if (!h->heap) {
free(h);
return NULL;
}
h->size = 0;
h->capacity = initial_capacity;
return h;
}
void timeout_heap_destroy(TimeoutHeap *h) {
if (h) {
free(h->heap);
free(h);
}
}
int timeout_heap_push(TimeoutHeap *h, TimeoutTime expiration, void *data) {
if (h->size == h->capacity) {
size_t new_cap = h->capacity ? h->capacity * 2 : 1;
TimeoutEntry *new_heap = realloc(h->heap, sizeof(TimeoutEntry) * new_cap);
if (!new_heap) return -1; // Allocation failed
h->heap = new_heap;
h->capacity = new_cap;
}
// Insert at end (0-based)
size_t idx = h->size++;
h->heap[idx].expiration = expiration;
h->heap[idx].data = data;
// Set initial index
update_index(data, idx);
// Bubble up (1-based)
bubble_up(h, idx + 1);
return 0;
}
static void remove_root(TimeoutHeap *h) {
if (h->size == 0) return;
// Move last to root
size_t last = --h->size;
h->heap[0] = h->heap[last];
// Update index for new root
update_index(h->heap[0].data, 0);
// Heapify down (1-based)
if (h->size > 0) {
heapify_down(h, 1);
}
}
int timeout_heap_peek(TimeoutHeap *h, TimeoutEntry *out) {
if (h->size == 0) return -1;
*out = h->heap[0];
return 0;
}
int timeout_heap_pop(TimeoutHeap *h, TimeoutEntry *out) {
if (h->size == 0) return -1;
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "timeout_heap_pop: entering, size=%zu", h->size);
*out = h->heap[0];
remove_root(h);
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "timeout_heap_pop: returning element, data=%p", out->data);
return 0;
}
int timeout_heap_remove(TimeoutHeap *h, void *data) {
struct timeout_node *node = (struct timeout_node *)data;
size_t idx = node->heap_index;
if (idx == SIZE_MAX || idx >= h->size || h->heap[idx].data != data) {
return -1; // Not found or invalid
}
// Swap with last
size_t last = --h->size;
if (idx != last) {
TimeoutEntry temp = h->heap[idx];
h->heap[idx] = h->heap[last];
h->heap[last] = temp;
// Update indices
update_index(h->heap[idx].data, idx);
update_index(h->heap[last].data, last);
// Heapify down and up to restore property
heapify_down(h, idx + 1);
bubble_up(h, idx + 1);
}
// Invalidate index
node->heap_index = SIZE_MAX;
return 0;
}

75
lib/timeout_heap.h1

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

2
lib/u_async.c

@ -394,6 +394,8 @@ void uasync_mainloop(struct UASYNC* ua) {
// Instance version
void uasync_poll(struct UASYNC* ua, int timeout_tb) {
if (!ua) return;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "async: mainloop: event waiting");
/* Process expired timeouts */
process_timeouts(ua);

834
lib/u_async.c.backup2

@ -1,834 +0,0 @@
// uasync.c
#include "u_async.h"
#include "debug_config.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <poll.h>
#include <limits.h>
#include <fcntl.h>
// Timeout node with safe cancellation
struct timeout_node {
void* arg;
timeout_callback_t callback;
uint64_t expiration_ms; // absolute expiration time in milliseconds
struct UASYNC* ua; // Pointer back to uasync instance for counter updates
int cancelled; // Cancellation flag
};
// Socket node with array-based storage
struct socket_node {
int fd;
socket_callback_t read_cbk;
socket_callback_t write_cbk;
socket_callback_t except_cbk;
void* user_data;
int active; // 1 if socket is active, 0 if freed (for reuse)
};
// Array-based socket management for O(1) operations
struct socket_array {
struct socket_node* sockets; // Dynamic array of socket nodes
int* fd_to_index; // FD to array index mapping
int* index_to_fd; // Array index to FD mapping
int capacity; // Total allocated capacity
int count; // Number of active sockets
int max_fd; // Maximum FD for bounds checking
};
static struct socket_array* socket_array_create(int initial_capacity);
static void socket_array_destroy(struct socket_array* sa);
static int socket_array_add(struct socket_array* sa, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data);
static int socket_array_remove(struct socket_array* sa, int fd);
static struct socket_node* socket_array_get(struct socket_array* sa, int fd);
static int socket_array_build_pollfd(struct socket_array* sa, struct pollfd* fds, int max_fds);
// No global instance - each module must use its own struct UASYNC instance
// Array-based socket management implementation
static struct socket_array* socket_array_create(int initial_capacity) {
if (initial_capacity < 4) initial_capacity = 4; // Minimum capacity
struct socket_array* sa = malloc(sizeof(struct socket_array));
if (!sa) return NULL;
sa->sockets = calloc(initial_capacity, sizeof(struct socket_node));
sa->fd_to_index = calloc(initial_capacity, sizeof(int));
sa->index_to_fd = calloc(initial_capacity, sizeof(int));
if (!sa->sockets || !sa->fd_to_index || !sa->index_to_fd) {
free(sa->sockets);
free(sa->fd_to_index);
free(sa->index_to_fd);
free(sa);
return NULL;
}
// Initialize mapping arrays to -1 (invalid)
for (int i = 0; i < initial_capacity; i++) {
sa->fd_to_index[i] = -1;
sa->index_to_fd[i] = -1;
sa->sockets[i].fd = -1;
sa->sockets[i].active = 0;
}
sa->capacity = initial_capacity;
sa->count = 0;
sa->max_fd = -1;
return sa;
}
static void socket_array_destroy(struct socket_array* sa) {
if (!sa) return;
free(sa->sockets);
free(sa->fd_to_index);
free(sa->index_to_fd);
free(sa);
}
static int socket_array_add(struct socket_array* sa, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data) {
if (!sa || fd < 0 || fd >= FD_SETSIZE) return -1;
if (fd >= sa->capacity) {
// Need to resize - double the capacity
int new_capacity = sa->capacity * 2;
if (fd >= new_capacity) new_capacity = fd + 16; // Ensure enough space
struct socket_node* new_sockets = realloc(sa->sockets, new_capacity * sizeof(struct socket_node));
int* new_fd_to_index = realloc(sa->fd_to_index, new_capacity * sizeof(int));
int* new_index_to_fd = realloc(sa->index_to_fd, new_capacity * sizeof(int));
if (!new_sockets || !new_fd_to_index || !new_index_to_fd) {
// Allocation failed
free(new_sockets);
free(new_fd_to_index);
free(new_index_to_fd);
return -1;
}
// Initialize new elements
for (int i = sa->capacity; i < new_capacity; i++) {
new_fd_to_index[i] = -1;
new_index_to_fd[i] = -1;
new_sockets[i].fd = -1;
new_sockets[i].active = 0;
}
sa->sockets = new_sockets;
sa->fd_to_index = new_fd_to_index;
sa->index_to_fd = new_index_to_fd;
sa->capacity = new_capacity;
}
// Check if FD already exists
if (sa->fd_to_index[fd] != -1) return -1; // FD already exists
// Find first free slot
int index = -1;
for (int i = 0; i < sa->capacity; i++) {
if (!sa->sockets[i].active) {
index = i;
break;
}
}
if (index == -1) return -1; // No free slots (shouldn't happen)
// Add the socket
sa->sockets[index].fd = fd;
sa->sockets[index].read_cbk = read_cbk;
sa->sockets[index].write_cbk = write_cbk;
sa->sockets[index].except_cbk = except_cbk;
sa->sockets[index].user_data = user_data;
sa->sockets[index].active = 1;
sa->fd_to_index[fd] = index;
sa->index_to_fd[index] = fd;
sa->count++;
if (fd > sa->max_fd) sa->max_fd = fd;
return index;
}
static int socket_array_remove(struct socket_array* sa, int fd) {
if (!sa || fd < 0 || fd >= sa->capacity) return -1;
int index = sa->fd_to_index[fd];
if (index == -1 || !sa->sockets[index].active) return -1; // FD not found
// Mark as inactive
sa->sockets[index].active = 0;
sa->sockets[index].fd = -1;
sa->fd_to_index[fd] = -1;
sa->index_to_fd[index] = -1;
sa->count--;
return 0;
}
static struct socket_node* socket_array_get(struct socket_array* sa, int fd) {
if (!sa || fd < 0 || fd >= sa->capacity) return NULL;
int index = sa->fd_to_index[fd];
if (index == -1 || !sa->sockets[index].active) return NULL;
return &sa->sockets[index];
}
static int socket_array_build_pollfd(struct socket_array* sa, struct pollfd* fds, int max_fds) {
if (!sa || !fds || max_fds <= 0) return 0;
int count = 0;
for (int i = 0; i < sa->capacity && count < max_fds; i++) {
if (sa->sockets[i].active) {
fds[count].fd = sa->sockets[i].fd;
fds[count].events = 0;
if (sa->sockets[i].read_cbk) fds[count].events |= POLLIN;
if (sa->sockets[i].write_cbk) fds[count].events |= POLLOUT;
if (sa->sockets[i].except_cbk) fds[count].events |= POLLERR;
fds[count].revents = 0;
count++;
}
}
return count;
}
// Callback to free timeout node and update counters
static void timeout_node_free_callback(void* user_data, void* data) {
struct UASYNC* ua = (struct UASYNC*)user_data;
struct timeout_node* node = (struct timeout_node*)data;
(void)node; // Not used directly, but keep for consistency
ua->timer_free_count++;
free(data);
}
// Helper to get current time
static void get_current_time(struct timeval* tv) {
gettimeofday(tv, NULL);
}
// Drain wakeup pipe - read all available bytes
static void drain_wakeup_pipe(struct UASYNC* ua) {
if (!ua || !ua->wakeup_initialized) return;
char buf[64];
while (1) {
ssize_t n = read(ua->wakeup_pipe[0], buf, sizeof(buf));
if (n <= 0) break;
}
}
// Helper to add timeval: tv += dt (timebase units)
static void timeval_add_tb(struct timeval* tv, int dt) {
tv->tv_usec += (dt % 10000) * 100;
tv->tv_sec += dt / 10000 + tv->tv_usec / 1000000;
tv->tv_usec %= 1000000;
}
// Convert timeval to milliseconds (uint64_t)
static uint64_t timeval_to_ms(const struct timeval* tv) {
return (uint64_t)tv->tv_sec * 1000ULL + (uint64_t)tv->tv_usec / 1000ULL;
}
// Simplified timeout handling without reference counting
// Process expired timeouts with safe cancellation
static void process_timeouts(struct UASYNC* ua) {
if (!ua || !ua->timeout_heap) return;
struct timeval now_tv;
get_current_time(&now_tv);
uint64_t now_ms = timeval_to_ms(&now_tv);
while (1) {
TimeoutEntry entry;
if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) break;
if (entry.expiration > now_ms) break;
// Pop the expired timeout
timeout_heap_pop(ua->timeout_heap, &entry);
struct timeout_node* node = (struct timeout_node*)entry.data;
if (node && node->callback && !node->cancelled) {
// Execute callback only if not cancelled
node->callback(node->arg);
}
// Always free the node after processing
if (node && node->ua) {
node->ua->timer_free_count++;
}
free(node);
}
}
// Compute time to next timeout
static void get_next_timeout(struct UASYNC* ua, struct timeval* tv) {
if (!ua || !ua->timeout_heap) {
tv->tv_sec = 0;
tv->tv_usec = 0;
return;
}
TimeoutEntry entry;
if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) {
tv->tv_sec = 0;
tv->tv_usec = 0;
return;
}
struct timeval now_tv;
get_current_time(&now_tv);
uint64_t now_ms = timeval_to_ms(&now_tv);
if (entry.expiration <= now_ms) {
tv->tv_sec = 0;
tv->tv_usec = 0;
return;
}
uint64_t delta_ms = entry.expiration - now_ms;
if (delta_ms > 86400000) { // Cap at 1 day to avoid overflow
delta_ms = 86400000;
}
tv->tv_sec = delta_ms / 1000;
tv->tv_usec = (delta_ms % 1000) * 1000;
}
// Instance version
void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* arg, timeout_callback_t callback) {
if (!ua || timeout_tb < 0 || !callback) return NULL;
if (!ua->timeout_heap) return NULL;
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: ua=%p, timeout=%d tb, arg=%p, callback=%p",
ua, timeout_tb, arg, callback);
struct timeout_node* node = malloc(sizeof(struct timeout_node));
if (!node) {
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to allocate node");
return NULL;
}
ua->timer_alloc_count++;
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: allocated node %p (alloc_count=%zu)",
node, ua->timer_alloc_count);
node->arg = arg;
node->callback = callback;
node->ua = ua;
node->cancelled = 0;
// Calculate expiration time in milliseconds
struct timeval now;
get_current_time(&now);
timeval_add_tb(&now, timeout_tb);
node->expiration_ms = timeval_to_ms(&now);
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: node %p expires at %llu ms",
node, (unsigned long long)node->expiration_ms);
// Add to heap
if (timeout_heap_push(ua->timeout_heap, node->expiration_ms, node) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to push to heap");
free(node);
ua->timer_free_count++; // Balance the alloc counter
return NULL;
}
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: successfully created timer %p", node);
return node;
}
// Instance version
err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id) {
if (!ua || !t_id || !ua->timeout_heap) {
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: invalid parameters ua=%p, t_id=%p, heap=%p",
ua, t_id, ua ? ua->timeout_heap : NULL);
return ERR_FAIL;
}
struct timeout_node* node = (struct timeout_node*)t_id;
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: ua=%p, t_id=%p, node=%p, expires=%llu ms",
ua, t_id, node, (unsigned long long)node->expiration_ms);
// Try to cancel from heap first
if (timeout_heap_cancel(ua->timeout_heap, node->expiration_ms, node) == 0) {
// Successfully removed from heap - mark as cancelled
node->cancelled = 1;
node->callback = NULL;
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: successfully cancelled timer %p from heap", node);
return ERR_OK;
// If not found in heap (maybe already expired and being processed)
// We still need to mark it as cancelled to prevent callback execution
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: timer %p not found in heap, marking as cancelled", node);
node->cancelled = 1;
node->callback = NULL;
return ERR_OK;
} // Successfully cancelled (marked)
}
// Instance version
void* uasync_add_socket(struct UASYNC* ua, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data) {
if (!ua || fd < 0 || fd >= FD_SETSIZE) return NULL; // Bounds check
int index = socket_array_add(ua->sockets, fd, read_cbk, write_cbk, except_cbk, user_data);
if (index < 0) return NULL;
ua->socket_alloc_count++;
// Return pointer to the socket node (same as before for API compatibility)
return &ua->sockets->sockets[index];
}
// Instance version
err_t uasync_remove_socket(struct UASYNC* ua, void* s_id) {
if (!ua || !s_id) return ERR_FAIL;
struct socket_node* node = (struct socket_node*)s_id;
if (node->fd < 0) return ERR_FAIL; // Invalid node
int result = socket_array_remove(ua->sockets, node->fd);
if (result != 0) return ERR_FAIL;
ua->socket_free_count++;
return ERR_OK;
}
void uasync_mainloop(struct UASYNC* ua) {
while (1) {
uasync_poll(ua, -1); /* infinite timeout */
}
}
// Instance version
void uasync_poll(struct UASYNC* ua, int timeout_tb) {
if (!ua) return;
/* Process expired timeouts */
process_timeouts(ua);
/* Compute timeout for poll in milliseconds */
int timeout_ms = -1; // infinite by default
// Get next timeout from heap
struct timeval tv;
get_next_timeout(ua, &tv);
if (tv.tv_sec > 0 || tv.tv_usec > 0 || (ua->timeout_heap && ua->timeout_heap->size > 0)) {
// Convert timeval to milliseconds, cap at INT_MAX
uint64_t ms = (uint64_t)tv.tv_sec * 1000ULL + (uint64_t)tv.tv_usec / 1000ULL;
if (ms > INT_MAX) ms = INT_MAX;
timeout_ms = (int)ms;
}
/* If timeout_tb >= 0, compute timeout as min(timeout_tb, existing timer) */
if (timeout_tb >= 0) {
// Convert timebase (0.1 ms) to milliseconds
int user_timeout_ms = timeout_tb / 10;
if (timeout_tb % 10 != 0) user_timeout_ms++; // round up
if (timeout_ms < 0 || user_timeout_ms < timeout_ms) {
timeout_ms = user_timeout_ms;
}
}
/* Build pollfd array from socket array - O(1) per socket */
int socket_count = ua->sockets ? ua->sockets->count : 0;
int wakeup_fd_present = ua->wakeup_initialized ? 1 : 0;
int total_fds = socket_count + wakeup_fd_present;
if (total_fds == 0) {
/* No sockets and no wakeup fd, just wait for timeout */
if (timeout_ms >= 0) {
/* usleep would be better but we just call poll with empty set */
struct pollfd dummy;
poll(&dummy, 0, timeout_ms);
} else {
/* Infinite timeout with no sockets - should not happen in practice */
return;
}
/* Check timeouts again after sleep */
process_timeouts(ua);
return;
}
struct pollfd* fds = malloc(total_fds * sizeof(struct pollfd));
struct socket_node** nodes = NULL;
if (socket_count > 0) {
nodes = malloc(socket_count * sizeof(struct socket_node*));
}
if (!fds || (socket_count > 0 && !nodes)) {
free(fds);
free(nodes);
return; /* out of memory */
}
/* Fill arrays */
int idx = 0;
/* Add wakeup fd first if present */
if (wakeup_fd_present) {
fds[idx].fd = ua->wakeup_pipe[0];
fds[idx].events = POLLIN;
fds[idx].revents = 0;
idx++;
}
/* Add socket fds using efficient array traversal */
int node_idx = 0;
for (int i = 0; i < ua->sockets->capacity && node_idx < socket_count; i++) {
if (ua->sockets->sockets[i].active) {
struct socket_node* cur = &ua->sockets->sockets[i];
fds[idx].fd = cur->fd;
fds[idx].events = 0;
fds[idx].revents = 0;
if (cur->read_cbk) fds[idx].events |= POLLIN;
if (cur->write_cbk) fds[idx].events |= POLLOUT;
if (cur->except_cbk) fds[idx].events |= POLLPRI;
if (nodes) {
nodes[node_idx] = cur;
}
idx++;
node_idx++;
}
}
/* Call poll */
int ret = poll(fds, total_fds, timeout_ms);
if (ret < 0) {
if (errno == EINTR) {
free(fds);
free(nodes);
return;
}
perror("poll");
free(fds);
free(nodes);
return;
}
/* Process timeouts that may have expired during poll */
process_timeouts(ua);
/* Process socket events */
if (ret > 0) {
for (int i = 0; i < total_fds; i++) {
if (fds[i].revents == 0) continue;
/* Handle wakeup fd separately */
if (wakeup_fd_present && i == 0) {
if (fds[i].revents & POLLIN) {
drain_wakeup_pipe(ua);
}
continue;
}
/* Socket event */
int socket_idx = i - wakeup_fd_present;
struct socket_node* node = nodes[socket_idx];
/* Check for error conditions first */
if (fds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
/* Treat as exceptional condition */
if (node->except_cbk) {
node->except_cbk(node->fd, node->user_data);
}
}
/* Exceptional data (out-of-band) */
if (fds[i].revents & POLLPRI) {
if (node->except_cbk) {
node->except_cbk(node->fd, node->user_data);
}
}
/* Read readiness */
if (fds[i].revents & POLLIN) {
if (node->read_cbk) {
node->read_cbk(node->fd, node->user_data);
}
}
/* Write readiness */
if (fds[i].revents & POLLOUT) {
if (node->write_cbk) {
node->write_cbk(node->fd, node->user_data);
}
}
}
}
free(fds);
free(nodes);
}
// ========== Instance management functions ==========
struct UASYNC* uasync_create(void) {
// Initialize debug system on first use
static int debug_initialized = 0;
if (!debug_initialized) {
debug_config_init();
debug_initialized = 1;
}
struct UASYNC* ua = malloc(sizeof(struct UASYNC));
if (!ua) return NULL;
memset(ua, 0, sizeof(struct UASYNC));
ua->wakeup_pipe[0] = -1;
ua->wakeup_pipe[1] = -1;
ua->wakeup_initialized = 0;
// Create wakeup pipe
if (pipe(ua->wakeup_pipe) < 0) {
DEBUG_WARN(DEBUG_CATEGORY_UASYNC, "Failed to create wakeup pipe: %s", strerror(errno));
// Continue without wakeup mechanism
ua->wakeup_pipe[0] = -1;
ua->wakeup_pipe[1] = -1;
} else {
ua->wakeup_initialized = 1;
// Set non-blocking on read end to avoid blocking if pipe is full
int flags = fcntl(ua->wakeup_pipe[0], F_GETFL, 0);
if (flags >= 0) {
fcntl(ua->wakeup_pipe[0], F_SETFL, flags | O_NONBLOCK);
}
}
ua->sockets = socket_array_create(16);
if (!ua->sockets) {
if (ua->wakeup_initialized) {
close(ua->wakeup_pipe[0]);
close(ua->wakeup_pipe[1]);
}
free(ua);
return NULL;
}
ua->timeout_heap = timeout_heap_create(16);
if (!ua->timeout_heap) {
socket_array_destroy(ua->sockets);
if (ua->wakeup_initialized) {
close(ua->wakeup_pipe[0]);
close(ua->wakeup_pipe[1]);
}
free(ua);
return NULL;
}
// Set callback to free timeout nodes and update counters
timeout_heap_set_free_callback(ua->timeout_heap, ua, timeout_node_free_callback);
return ua;
}
// Print all resources for debugging
void uasync_print_resources(struct UASYNC* ua, const char* prefix) {
if (!ua) {
printf("%s: NULL uasync instance\n", prefix);
return;
}
printf("\n🔍 %s: UASYNC Resource Report for %p\n", prefix, ua);
printf(" Timer Statistics: allocated=%zu, freed=%zu, active=%zd\n",
ua->timer_alloc_count, ua->timer_free_count,
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count));
printf(" Socket Statistics: allocated=%zu, freed=%zu, active=%zd\n",
ua->socket_alloc_count, ua->socket_free_count,
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count));
// Показать активные таймеры
if (ua->timeout_heap) {
size_t active_timers = 0;
TimeoutEntry entry;
// Создаем временную копию кучи для подсчета
TimeoutHeap* temp_heap = timeout_heap_create(16);
if (temp_heap) {
// Копируем все активные таймеры
while (timeout_heap_pop(ua->timeout_heap, &entry) == 0) {
if (!entry.deleted) {
active_timers++;
struct timeout_node* node = (struct timeout_node*)entry.data;
printf(" Timer: node=%p, expires=%llu ms, cancelled=%d\n",
node, (unsigned long long)entry.expiration, node->cancelled);
}
timeout_heap_push(temp_heap, entry.expiration, entry.data);
}
// Возвращаем таймеры обратно
while (timeout_heap_pop(temp_heap, &entry) == 0) {
timeout_heap_push(ua->timeout_heap, entry.expiration, entry.data);
}
timeout_heap_destroy(temp_heap);
}
printf(" Active timers in heap: %zu\n", active_timers);
}
// Показать активные сокеты
if (ua->sockets) {
int active_sockets = 0;
printf(" Socket array capacity: %d, active: %d\n",
ua->sockets->capacity, ua->sockets->count);
for (int i = 0; i < ua->sockets->capacity; i++) {
if (ua->sockets->sockets[i].active) {
active_sockets++;
printf(" Socket: fd=%d, active=%d\n",
ua->sockets->sockets[i].fd,
ua->sockets->sockets[i].active);
}
}
printf(" Total active sockets: %d\n", active_sockets);
}
printf("🔚 %s: End of resource report\n\n", prefix);
}
void uasync_destroy(struct UASYNC* ua) {
if (!ua) return;
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: starting cleanup for ua=%p", ua);
// Диагностика ресурсов перед очисткой
uasync_print_resources(ua, "BEFORE_DESTROY");
// Check for potential memory leaks
if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory leaks detected before cleanup: timers %zu/%zu, sockets %zu/%zu",
ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count);
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Timer leak: allocated=%zu, freed=%zu, diff=%zd",
ua->timer_alloc_count, ua->timer_free_count,
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count));
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Socket leak: allocated=%zu, freed=%zu, diff=%zd",
ua->socket_alloc_count, ua->socket_free_count,
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count));
// Continue cleanup, will abort after if leaks remain
}
// Free all remaining timeouts
if (ua->timeout_heap) {
size_t freed_count = 0;
while (1) {
TimeoutEntry entry;
if (timeout_heap_pop(ua->timeout_heap, &entry) != 0) break;
struct timeout_node* node = (struct timeout_node*)entry.data;
DEBUG_TRACE(DEBUG_CATEGORY_TIMERS, "uasync_destroy: freeing timer node %p (expired=%llu ms)",
node, (unsigned long long)entry.expiration);
ua->timer_free_count++;
freed_count++;
free(node);
}
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: freed %zu timer nodes in destroy, heap freed_count = %zu",
freed_count, ua->timeout_heap->freed_count);
timeout_heap_destroy(ua->timeout_heap);
}
// Free all socket nodes using array approach
if (ua->sockets) {
// Count and free all active sockets
int freed_count = 0;
for (int i = 0; i < ua->sockets->capacity; i++) {
if (ua->sockets->sockets[i].active) {
ua->socket_free_count++;
freed_count++;
}
}
DEBUG_DEBUG(DEBUG_CATEGORY_MEMORY, "Freed %d socket nodes in destroy", freed_count);
socket_array_destroy(ua->sockets);
}
// Close wakeup pipe
if (ua->wakeup_initialized) {
close(ua->wakeup_pipe[0]);
close(ua->wakeup_pipe[1]);
}
// Final leak check
if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory leaks detected after cleanup: timers %zu/%zu, sockets %zu/%zu",
ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count);
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "FINAL Timer leak: allocated=%zu, freed=%zu, diff=%zd",
ua->timer_alloc_count, ua->timer_free_count,
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count));
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "FINAL Socket leak: allocated=%zu, freed=%zu, diff=%zd",
ua->socket_alloc_count, ua->socket_free_count,
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count));
abort();
}
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: completed successfully for ua=%p", ua);
free(ua);
}
void uasync_init_instance(struct UASYNC* ua) {
if (!ua) return;
// Initialize socket array if not present
if (!ua->sockets) {
ua->sockets = socket_array_create(16);
}
if (!ua->timeout_heap) {
ua->timeout_heap = timeout_heap_create(16);
if (ua->timeout_heap) {
timeout_heap_set_free_callback(ua->timeout_heap, ua, timeout_node_free_callback);
}
}
}
// Debug statistics
void uasync_get_stats(struct UASYNC* ua, size_t* timer_alloc, size_t* timer_free, size_t* socket_alloc, size_t* socket_free) {
if (!ua) return;
if (timer_alloc) *timer_alloc = ua->timer_alloc_count;
if (timer_free) *timer_free = ua->timer_free_count;
if (socket_alloc) *socket_alloc = ua->socket_alloc_count;
if (socket_free) *socket_free = ua->socket_free_count;
}
// Get global instance for backward compatibility
// Wakeup mechanism
int uasync_wakeup(struct UASYNC* ua) {
if (!ua || !ua->wakeup_initialized) return -1;
char byte = 0;
ssize_t ret = write(ua->wakeup_pipe[1], &byte, 1);
if (ret != 1) {
// Don't print error from signal handler
return -1;
}
return 0;
}
int uasync_get_wakeup_fd(struct UASYNC* ua) {
if (!ua || !ua->wakeup_initialized) return -1;
return ua->wakeup_pipe[1];
}

830
lib/u_async.c1

@ -1,830 +0,0 @@
// uasync.c
#include "u_async.h"
#include "debug_config.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <poll.h>
#include <limits.h>
#include <fcntl.h>
// Timeout node with safe cancellation
struct timeout_node {
void* arg;
timeout_callback_t callback;
uint64_t expiration_ms; // absolute expiration time in milliseconds
struct UASYNC* ua; // Pointer back to uasync instance for counter updates
int cancelled; // Cancellation flag
};
// Socket node with array-based storage
struct socket_node {
int fd;
socket_callback_t read_cbk;
socket_callback_t write_cbk;
socket_callback_t except_cbk;
void* user_data;
int active; // 1 if socket is active, 0 if freed (for reuse)
};
// Array-based socket management for O(1) operations
struct socket_array {
struct socket_node* sockets; // Dynamic array of socket nodes
int* fd_to_index; // FD to array index mapping
int* index_to_fd; // Array index to FD mapping
int capacity; // Total allocated capacity
int count; // Number of active sockets
int max_fd; // Maximum FD for bounds checking
};
static struct socket_array* socket_array_create(int initial_capacity);
static void socket_array_destroy(struct socket_array* sa);
static int socket_array_add(struct socket_array* sa, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data);
static int socket_array_remove(struct socket_array* sa, int fd);
static struct socket_node* socket_array_get(struct socket_array* sa, int fd);
static int socket_array_build_pollfd(struct socket_array* sa, struct pollfd* fds, int max_fds);
// No global instance - each module must use its own struct UASYNC instance
// Array-based socket management implementation
static struct socket_array* socket_array_create(int initial_capacity) {
if (initial_capacity < 4) initial_capacity = 4; // Minimum capacity
struct socket_array* sa = malloc(sizeof(struct socket_array));
if (!sa) return NULL;
sa->sockets = calloc(initial_capacity, sizeof(struct socket_node));
sa->fd_to_index = calloc(initial_capacity, sizeof(int));
sa->index_to_fd = calloc(initial_capacity, sizeof(int));
if (!sa->sockets || !sa->fd_to_index || !sa->index_to_fd) {
free(sa->sockets);
free(sa->fd_to_index);
free(sa->index_to_fd);
free(sa);
return NULL;
}
// Initialize mapping arrays to -1 (invalid)
for (int i = 0; i < initial_capacity; i++) {
sa->fd_to_index[i] = -1;
sa->index_to_fd[i] = -1;
sa->sockets[i].fd = -1;
sa->sockets[i].active = 0;
}
sa->capacity = initial_capacity;
sa->count = 0;
sa->max_fd = -1;
return sa;
}
static void socket_array_destroy(struct socket_array* sa) {
if (!sa) return;
free(sa->sockets);
free(sa->fd_to_index);
free(sa->index_to_fd);
free(sa);
}
static int socket_array_add(struct socket_array* sa, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data) {
if (!sa || fd < 0 || fd >= FD_SETSIZE) return -1;
if (fd >= sa->capacity) {
// Need to resize - double the capacity
int new_capacity = sa->capacity * 2;
if (fd >= new_capacity) new_capacity = fd + 16; // Ensure enough space
struct socket_node* new_sockets = realloc(sa->sockets, new_capacity * sizeof(struct socket_node));
int* new_fd_to_index = realloc(sa->fd_to_index, new_capacity * sizeof(int));
int* new_index_to_fd = realloc(sa->index_to_fd, new_capacity * sizeof(int));
if (!new_sockets || !new_fd_to_index || !new_index_to_fd) {
// Allocation failed
free(new_sockets);
free(new_fd_to_index);
free(new_index_to_fd);
return -1;
}
// Initialize new elements
for (int i = sa->capacity; i < new_capacity; i++) {
new_fd_to_index[i] = -1;
new_index_to_fd[i] = -1;
new_sockets[i].fd = -1;
new_sockets[i].active = 0;
}
sa->sockets = new_sockets;
sa->fd_to_index = new_fd_to_index;
sa->index_to_fd = new_index_to_fd;
sa->capacity = new_capacity;
}
// Check if FD already exists
if (sa->fd_to_index[fd] != -1) return -1; // FD already exists
// Find first free slot
int index = -1;
for (int i = 0; i < sa->capacity; i++) {
if (!sa->sockets[i].active) {
index = i;
break;
}
}
if (index == -1) return -1; // No free slots (shouldn't happen)
// Add the socket
sa->sockets[index].fd = fd;
sa->sockets[index].read_cbk = read_cbk;
sa->sockets[index].write_cbk = write_cbk;
sa->sockets[index].except_cbk = except_cbk;
sa->sockets[index].user_data = user_data;
sa->sockets[index].active = 1;
sa->fd_to_index[fd] = index;
sa->index_to_fd[index] = fd;
sa->count++;
if (fd > sa->max_fd) sa->max_fd = fd;
return index;
}
static int socket_array_remove(struct socket_array* sa, int fd) {
if (!sa || fd < 0 || fd >= sa->capacity) return -1;
int index = sa->fd_to_index[fd];
if (index == -1 || !sa->sockets[index].active) return -1; // FD not found
// Mark as inactive
sa->sockets[index].active = 0;
sa->sockets[index].fd = -1;
sa->fd_to_index[fd] = -1;
sa->index_to_fd[index] = -1;
sa->count--;
return 0;
}
static struct socket_node* socket_array_get(struct socket_array* sa, int fd) {
if (!sa || fd < 0 || fd >= sa->capacity) return NULL;
int index = sa->fd_to_index[fd];
if (index == -1 || !sa->sockets[index].active) return NULL;
return &sa->sockets[index];
}
static int socket_array_build_pollfd(struct socket_array* sa, struct pollfd* fds, int max_fds) {
if (!sa || !fds || max_fds <= 0) return 0;
int count = 0;
for (int i = 0; i < sa->capacity && count < max_fds; i++) {
if (sa->sockets[i].active) {
fds[count].fd = sa->sockets[i].fd;
fds[count].events = 0;
if (sa->sockets[i].read_cbk) fds[count].events |= POLLIN;
if (sa->sockets[i].write_cbk) fds[count].events |= POLLOUT;
if (sa->sockets[i].except_cbk) fds[count].events |= POLLERR;
fds[count].revents = 0;
count++;
}
}
return count;
}
// Callback to free timeout node and update counters
static void timeout_node_free_callback(void* user_data, void* data) {
struct UASYNC* ua = (struct UASYNC*)user_data;
struct timeout_node* node = (struct timeout_node*)data;
(void)node; // Not used directly, but keep for consistency
ua->timer_free_count++;
free(data);
}
// Helper to get current time
static void get_current_time(struct timeval* tv) {
gettimeofday(tv, NULL);
}
// Drain wakeup pipe - read all available bytes
static void drain_wakeup_pipe(struct UASYNC* ua) {
if (!ua || !ua->wakeup_initialized) return;
char buf[64];
while (1) {
ssize_t n = read(ua->wakeup_pipe[0], buf, sizeof(buf));
if (n <= 0) break;
}
}
// Helper to add timeval: tv += dt (timebase units)
static void timeval_add_tb(struct timeval* tv, int dt) {
tv->tv_usec += (dt % 10000) * 100;
tv->tv_sec += dt / 10000 + tv->tv_usec / 1000000;
tv->tv_usec %= 1000000;
}
// Convert timeval to milliseconds (uint64_t)
static uint64_t timeval_to_ms(const struct timeval* tv) {
return (uint64_t)tv->tv_sec * 1000ULL + (uint64_t)tv->tv_usec / 1000ULL;
}
// Simplified timeout handling without reference counting
// Process expired timeouts with safe cancellation
static void process_timeouts(struct UASYNC* ua) {
if (!ua || !ua->timeout_heap) return;
struct timeval now_tv;
get_current_time(&now_tv);
uint64_t now_ms = timeval_to_ms(&now_tv);
while (1) {
TimeoutEntry entry;
if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) break;
if (entry.expiration > now_ms) break;
// Pop the expired timeout
timeout_heap_pop(ua->timeout_heap, &entry);
struct timeout_node* node = (struct timeout_node*)entry.data;
if (node && node->callback && !node->cancelled) {
// Execute callback only if not cancelled
node->callback(node->arg);
}
// Always free the node after processing
if (node && node->ua) {
node->ua->timer_free_count++;
}
free(node);
}
}
// Compute time to next timeout
static void get_next_timeout(struct UASYNC* ua, struct timeval* tv) {
if (!ua || !ua->timeout_heap) {
tv->tv_sec = 0;
tv->tv_usec = 0;
return;
}
TimeoutEntry entry;
if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) {
tv->tv_sec = 0;
tv->tv_usec = 0;
return;
}
struct timeval now_tv;
get_current_time(&now_tv);
uint64_t now_ms = timeval_to_ms(&now_tv);
if (entry.expiration <= now_ms) {
tv->tv_sec = 0;
tv->tv_usec = 0;
return;
}
uint64_t delta_ms = entry.expiration - now_ms;
if (delta_ms > 86400000) { // Cap at 1 day to avoid overflow
delta_ms = 86400000;
}
tv->tv_sec = delta_ms / 1000;
tv->tv_usec = (delta_ms % 1000) * 1000;
}
// Instance version
void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* arg, timeout_callback_t callback) {
if (!ua || timeout_tb < 0 || !callback) return NULL;
if (!ua->timeout_heap) return NULL;
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: ua=%p, timeout=%d tb, arg=%p, callback=%p",
ua, timeout_tb, arg, callback);
struct timeout_node* node = malloc(sizeof(struct timeout_node));
if (!node) {
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to allocate node");
return NULL;
}
ua->timer_alloc_count++;
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: allocated node %p (alloc_count=%zu)",
node, ua->timer_alloc_count);
node->arg = arg;
node->callback = callback;
node->ua = ua;
node->cancelled = 0;
// Calculate expiration time in milliseconds
struct timeval now;
get_current_time(&now);
timeval_add_tb(&now, timeout_tb);
node->expiration_ms = timeval_to_ms(&now);
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: node %p expires at %llu ms",
node, (unsigned long long)node->expiration_ms);
// Add to heap
if (timeout_heap_push(ua->timeout_heap, node->expiration_ms, node) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to push to heap");
free(node);
ua->timer_free_count++; // Balance the alloc counter
return NULL;
}
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: successfully created timer %p", node);
return node;
}
// Instance version
err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id) {
if (!ua || !t_id || !ua->timeout_heap) {
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: invalid parameters ua=%p, t_id=%p, heap=%p",
ua, t_id, ua ? ua->timeout_heap : NULL);
return ERR_FAIL;
}
struct timeout_node* node = (struct timeout_node*)t_id;
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: ua=%p, t_id=%p, node=%p, expires=%llu ms",
ua, t_id, node, (unsigned long long)node->expiration_ms);
// Try to cancel from heap first
if (timeout_heap_cancel(ua->timeout_heap, node->expiration_ms, node) == 0) {
// Successfully removed from heap - mark as cancelled and update counter
node->cancelled = 1;
node->callback = NULL;
ua->timer_free_count++; // Update counter for cancelled timer
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: successfully cancelled timer %p from heap, free_count=%zu", node, ua->timer_free_count);
free(node); // Free the cancelled timer node
return ERR_OK;
}
node->cancelled = 1;
node->callback = NULL;
return ERR_OK;
} // Successfully cancelled (marked)
// Instance version
void* uasync_add_socket(struct UASYNC* ua, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data) {
if (!ua || fd < 0 || fd >= FD_SETSIZE) return NULL; // Bounds check
int index = socket_array_add(ua->sockets, fd, read_cbk, write_cbk, except_cbk, user_data);
if (index < 0) return NULL;
ua->socket_alloc_count++;
// Return pointer to the socket node (same as before for API compatibility)
return &ua->sockets->sockets[index];
}
// Instance version
err_t uasync_remove_socket(struct UASYNC* ua, void* s_id) {
if (!ua || !s_id) return ERR_FAIL;
struct socket_node* node = (struct socket_node*)s_id;
if (node->fd < 0) return ERR_FAIL; // Invalid node
int result = socket_array_remove(ua->sockets, node->fd);
if (result != 0) return ERR_FAIL;
ua->socket_free_count++;
return ERR_OK;
}
void uasync_mainloop(struct UASYNC* ua) {
while (1) {
uasync_poll(ua, -1); /* infinite timeout */
}
}
// Instance version
void uasync_poll(struct UASYNC* ua, int timeout_tb) {
if (!ua) return;
/* Process expired timeouts */
process_timeouts(ua);
/* Compute timeout for poll in milliseconds */
int timeout_ms = -1; // infinite by default
// Get next timeout from heap
struct timeval tv;
get_next_timeout(ua, &tv);
if (tv.tv_sec > 0 || tv.tv_usec > 0 || (ua->timeout_heap && ua->timeout_heap->size > 0)) {
// Convert timeval to milliseconds, cap at INT_MAX
uint64_t ms = (uint64_t)tv.tv_sec * 1000ULL + (uint64_t)tv.tv_usec / 1000ULL;
if (ms > INT_MAX) ms = INT_MAX;
timeout_ms = (int)ms;
}
/* If timeout_tb >= 0, compute timeout as min(timeout_tb, existing timer) */
if (timeout_tb >= 0) {
// Convert timebase (0.1 ms) to milliseconds
int user_timeout_ms = timeout_tb / 10;
if (timeout_tb % 10 != 0) user_timeout_ms++; // round up
if (timeout_ms < 0 || user_timeout_ms < timeout_ms) {
timeout_ms = user_timeout_ms;
}
}
/* Build pollfd array from socket array - O(1) per socket */
int socket_count = ua->sockets ? ua->sockets->count : 0;
int wakeup_fd_present = ua->wakeup_initialized ? 1 : 0;
int total_fds = socket_count + wakeup_fd_present;
if (total_fds == 0) {
/* No sockets and no wakeup fd, just wait for timeout */
if (timeout_ms >= 0) {
/* usleep would be better but we just call poll with empty set */
struct pollfd dummy;
poll(&dummy, 0, timeout_ms);
} else {
/* Infinite timeout with no sockets - should not happen in practice */
return;
}
/* Check timeouts again after sleep */
process_timeouts(ua);
return;
}
struct pollfd* fds = malloc(total_fds * sizeof(struct pollfd));
struct socket_node** nodes = NULL;
if (socket_count > 0) {
nodes = malloc(socket_count * sizeof(struct socket_node*));
}
if (!fds || (socket_count > 0 && !nodes)) {
free(fds);
free(nodes);
return; /* out of memory */
}
/* Fill arrays */
int idx = 0;
/* Add wakeup fd first if present */
if (wakeup_fd_present) {
fds[idx].fd = ua->wakeup_pipe[0];
fds[idx].events = POLLIN;
fds[idx].revents = 0;
idx++;
}
/* Add socket fds using efficient array traversal */
int node_idx = 0;
for (int i = 0; i < ua->sockets->capacity && node_idx < socket_count; i++) {
if (ua->sockets->sockets[i].active) {
struct socket_node* cur = &ua->sockets->sockets[i];
fds[idx].fd = cur->fd;
fds[idx].events = 0;
fds[idx].revents = 0;
if (cur->read_cbk) fds[idx].events |= POLLIN;
if (cur->write_cbk) fds[idx].events |= POLLOUT;
if (cur->except_cbk) fds[idx].events |= POLLPRI;
if (nodes) {
nodes[node_idx] = cur;
}
idx++;
node_idx++;
}
}
/* Call poll */
int ret = poll(fds, total_fds, timeout_ms);
if (ret < 0) {
if (errno == EINTR) {
free(fds);
free(nodes);
return;
}
perror("poll");
free(fds);
free(nodes);
return;
}
/* Process timeouts that may have expired during poll */
process_timeouts(ua);
/* Process socket events */
if (ret > 0) {
for (int i = 0; i < total_fds; i++) {
if (fds[i].revents == 0) continue;
/* Handle wakeup fd separately */
if (wakeup_fd_present && i == 0) {
if (fds[i].revents & POLLIN) {
drain_wakeup_pipe(ua);
}
continue;
}
/* Socket event */
int socket_idx = i - wakeup_fd_present;
struct socket_node* node = nodes[socket_idx];
/* Check for error conditions first */
if (fds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
/* Treat as exceptional condition */
if (node->except_cbk) {
node->except_cbk(node->fd, node->user_data);
}
}
/* Exceptional data (out-of-band) */
if (fds[i].revents & POLLPRI) {
if (node->except_cbk) {
node->except_cbk(node->fd, node->user_data);
}
}
/* Read readiness */
if (fds[i].revents & POLLIN) {
if (node->read_cbk) {
node->read_cbk(node->fd, node->user_data);
}
}
/* Write readiness */
if (fds[i].revents & POLLOUT) {
if (node->write_cbk) {
node->write_cbk(node->fd, node->user_data);
}
}
}
}
free(fds);
free(nodes);
}
// ========== Instance management functions ==========
struct UASYNC* uasync_create(void) {
// Initialize debug system on first use
static int debug_initialized = 0;
if (!debug_initialized) {
debug_config_init();
debug_initialized = 1;
}
struct UASYNC* ua = malloc(sizeof(struct UASYNC));
if (!ua) return NULL;
memset(ua, 0, sizeof(struct UASYNC));
ua->wakeup_pipe[0] = -1;
ua->wakeup_pipe[1] = -1;
ua->wakeup_initialized = 0;
// Create wakeup pipe
if (pipe(ua->wakeup_pipe) < 0) {
DEBUG_WARN(DEBUG_CATEGORY_UASYNC, "Failed to create wakeup pipe: %s", strerror(errno));
// Continue without wakeup mechanism
ua->wakeup_pipe[0] = -1;
ua->wakeup_pipe[1] = -1;
} else {
ua->wakeup_initialized = 1;
// Set non-blocking on read end to avoid blocking if pipe is full
int flags = fcntl(ua->wakeup_pipe[0], F_GETFL, 0);
if (flags >= 0) {
fcntl(ua->wakeup_pipe[0], F_SETFL, flags | O_NONBLOCK);
}
}
ua->sockets = socket_array_create(16);
if (!ua->sockets) {
if (ua->wakeup_initialized) {
close(ua->wakeup_pipe[0]);
close(ua->wakeup_pipe[1]);
}
free(ua);
return NULL;
}
ua->timeout_heap = timeout_heap_create(16);
if (!ua->timeout_heap) {
socket_array_destroy(ua->sockets);
if (ua->wakeup_initialized) {
close(ua->wakeup_pipe[0]);
close(ua->wakeup_pipe[1]);
}
free(ua);
return NULL;
}
// Set callback to free timeout nodes and update counters
timeout_heap_set_free_callback(ua->timeout_heap, ua, timeout_node_free_callback);
return ua;
}
// Print all resources for debugging
void uasync_print_resources(struct UASYNC* ua, const char* prefix) {
if (!ua) {
printf("%s: NULL uasync instance\n", prefix);
return;
}
printf("\n🔍 %s: UASYNC Resource Report for %p\n", prefix, ua);
printf(" Timer Statistics: allocated=%zu, freed=%zu, active=%zd\n",
ua->timer_alloc_count, ua->timer_free_count,
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count));
printf(" Socket Statistics: allocated=%zu, freed=%zu, active=%zd\n",
ua->socket_alloc_count, ua->socket_free_count,
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count));
// Показать активные таймеры
if (ua->timeout_heap) {
size_t active_timers = 0;
TimeoutEntry entry;
// Создаем временную копию кучи для подсчета
TimeoutHeap* temp_heap = timeout_heap_create(16);
if (temp_heap) {
// Копируем все активные таймеры
while (timeout_heap_pop(ua->timeout_heap, &entry) == 0) {
if (!entry.deleted) {
active_timers++;
struct timeout_node* node = (struct timeout_node*)entry.data;
printf(" Timer: node=%p, expires=%llu ms, cancelled=%d\n",
node, (unsigned long long)entry.expiration, node->cancelled);
}
timeout_heap_push(temp_heap, entry.expiration, entry.data);
}
// Возвращаем таймеры обратно
while (timeout_heap_pop(temp_heap, &entry) == 0) {
timeout_heap_push(ua->timeout_heap, entry.expiration, entry.data);
}
timeout_heap_destroy(temp_heap);
}
printf(" Active timers in heap: %zu\n", active_timers);
}
// Показать активные сокеты
if (ua->sockets) {
int active_sockets = 0;
printf(" Socket array capacity: %d, active: %d\n",
ua->sockets->capacity, ua->sockets->count);
for (int i = 0; i < ua->sockets->capacity; i++) {
if (ua->sockets->sockets[i].active) {
active_sockets++;
printf(" Socket: fd=%d, active=%d\n",
ua->sockets->sockets[i].fd,
ua->sockets->sockets[i].active);
}
}
printf(" Total active sockets: %d\n", active_sockets);
}
printf("🔚 %s: End of resource report\n\n", prefix);
}
void uasync_destroy(struct UASYNC* ua) {
if (!ua) return;
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: starting cleanup for ua=%p", ua);
// Диагностика ресурсов перед очисткой
uasync_print_resources(ua, "BEFORE_DESTROY");
// Check for potential memory leaks
if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory leaks detected before cleanup: timers %zu/%zu, sockets %zu/%zu",
ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count);
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Timer leak: allocated=%zu, freed=%zu, diff=%zd",
ua->timer_alloc_count, ua->timer_free_count,
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count));
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Socket leak: allocated=%zu, freed=%zu, diff=%zd",
ua->socket_alloc_count, ua->socket_free_count,
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count));
// Continue cleanup, will abort after if leaks remain
}
// Free all remaining timeouts
if (ua->timeout_heap) {
size_t freed_count = 0;
while (1) {
TimeoutEntry entry;
if (timeout_heap_pop(ua->timeout_heap, &entry) != 0) break;
struct timeout_node* node = (struct timeout_node*)entry.data;
DEBUG_TRACE(DEBUG_CATEGORY_TIMERS, "uasync_destroy: freeing timer node %p (expired=%llu ms)",
node, (unsigned long long)entry.expiration);
ua->timer_free_count++;
freed_count++;
free(node);
}
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: freed %zu timer nodes in destroy, heap freed_count = %zu",
freed_count, ua->timeout_heap->freed_count);
timeout_heap_destroy(ua->timeout_heap);
}
// Free all socket nodes using array approach
if (ua->sockets) {
// Count and free all active sockets
int freed_count = 0;
for (int i = 0; i < ua->sockets->capacity; i++) {
if (ua->sockets->sockets[i].active) {
ua->socket_free_count++;
freed_count++;
}
}
DEBUG_DEBUG(DEBUG_CATEGORY_MEMORY, "Freed %d socket nodes in destroy", freed_count);
socket_array_destroy(ua->sockets);
}
// Close wakeup pipe
if (ua->wakeup_initialized) {
close(ua->wakeup_pipe[0]);
close(ua->wakeup_pipe[1]);
}
// Final leak check
if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory leaks detected after cleanup: timers %zu/%zu, sockets %zu/%zu",
ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count);
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "FINAL Timer leak: allocated=%zu, freed=%zu, diff=%zd",
ua->timer_alloc_count, ua->timer_free_count,
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count));
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "FINAL Socket leak: allocated=%zu, freed=%zu, diff=%zd",
ua->socket_alloc_count, ua->socket_free_count,
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count));
abort();
}
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: completed successfully for ua=%p", ua);
free(ua);
}
void uasync_init_instance(struct UASYNC* ua) {
if (!ua) return;
// Initialize socket array if not present
if (!ua->sockets) {
ua->sockets = socket_array_create(16);
}
if (!ua->timeout_heap) {
ua->timeout_heap = timeout_heap_create(16);
if (ua->timeout_heap) {
timeout_heap_set_free_callback(ua->timeout_heap, ua, timeout_node_free_callback);
}
}
}
// Debug statistics
void uasync_get_stats(struct UASYNC* ua, size_t* timer_alloc, size_t* timer_free, size_t* socket_alloc, size_t* socket_free) {
if (!ua) return;
if (timer_alloc) *timer_alloc = ua->timer_alloc_count;
if (timer_free) *timer_free = ua->timer_free_count;
if (socket_alloc) *socket_alloc = ua->socket_alloc_count;
if (socket_free) *socket_free = ua->socket_free_count;
}
// Get global instance for backward compatibility
// Wakeup mechanism
int uasync_wakeup(struct UASYNC* ua) {
if (!ua || !ua->wakeup_initialized) return -1;
char byte = 0;
ssize_t ret = write(ua->wakeup_pipe[1], &byte, 1);
if (ret != 1) {
// Don't print error from signal handler
return -1;
}
return 0;
}
int uasync_get_wakeup_fd(struct UASYNC* ua) {
if (!ua || !ua->wakeup_initialized) return -1;
return ua->wakeup_pipe[1];
}

800
lib/u_async.c2

@ -1,800 +0,0 @@
// uasync.c
#include "u_async.h"
#include "debug_config.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <poll.h>
#include <limits.h>
#include <fcntl.h>
// Timeout node with safe cancellation
struct timeout_node {
void* arg;
timeout_callback_t callback;
uint64_t expiration_ms; // absolute expiration time in milliseconds
struct UASYNC* ua; // Pointer back to uasync instance for counter updates
int cancelled; // Cancellation flag
};
// Socket node with array-based storage
struct socket_node {
int fd;
socket_callback_t read_cbk;
socket_callback_t write_cbk;
socket_callback_t except_cbk;
void* user_data;
int active; // 1 if socket is active, 0 if freed (for reuse)
};
// Array-based socket management for O(1) operations
struct socket_array {
struct socket_node* sockets; // Dynamic array of socket nodes
int* fd_to_index; // FD to array index mapping
int* index_to_fd; // Array index to FD mapping
int capacity; // Total allocated capacity
int count; // Number of active sockets
int max_fd; // Maximum FD for bounds checking
};
static struct socket_array* socket_array_create(int initial_capacity);
static void socket_array_destroy(struct socket_array* sa);
static int socket_array_add(struct socket_array* sa, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data);
static int socket_array_remove(struct socket_array* sa, int fd);
static struct socket_node* socket_array_get(struct socket_array* sa, int fd);
// No global instance - each module must use its own struct UASYNC instance
// Array-based socket management implementation
static struct socket_array* socket_array_create(int initial_capacity) {
if (initial_capacity < 4) initial_capacity = 4; // Minimum capacity
struct socket_array* sa = malloc(sizeof(struct socket_array));
if (!sa) return NULL;
sa->sockets = calloc(initial_capacity, sizeof(struct socket_node));
sa->fd_to_index = calloc(initial_capacity, sizeof(int));
sa->index_to_fd = calloc(initial_capacity, sizeof(int));
if (!sa->sockets || !sa->fd_to_index || !sa->index_to_fd) {
free(sa->sockets);
free(sa->fd_to_index);
free(sa->index_to_fd);
free(sa);
return NULL;
}
// Initialize mapping arrays to -1 (invalid)
for (int i = 0; i < initial_capacity; i++) {
sa->fd_to_index[i] = -1;
sa->index_to_fd[i] = -1;
sa->sockets[i].fd = -1;
sa->sockets[i].active = 0;
}
sa->capacity = initial_capacity;
sa->count = 0;
sa->max_fd = -1;
return sa;
}
static void socket_array_destroy(struct socket_array* sa) {
if (!sa) return;
free(sa->sockets);
free(sa->fd_to_index);
free(sa->index_to_fd);
free(sa);
}
static int socket_array_add(struct socket_array* sa, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data) {
if (!sa || fd < 0 || fd >= FD_SETSIZE) return -1;
if (fd >= sa->capacity) {
// Need to resize - double the capacity
int new_capacity = sa->capacity * 2;
if (fd >= new_capacity) new_capacity = fd + 16; // Ensure enough space
struct socket_node* new_sockets = realloc(sa->sockets, new_capacity * sizeof(struct socket_node));
int* new_fd_to_index = realloc(sa->fd_to_index, new_capacity * sizeof(int));
int* new_index_to_fd = realloc(sa->index_to_fd, new_capacity * sizeof(int));
if (!new_sockets || !new_fd_to_index || !new_index_to_fd) {
// Allocation failed
free(new_sockets);
free(new_fd_to_index);
free(new_index_to_fd);
return -1;
}
// Initialize new elements
for (int i = sa->capacity; i < new_capacity; i++) {
new_fd_to_index[i] = -1;
new_index_to_fd[i] = -1;
new_sockets[i].fd = -1;
new_sockets[i].active = 0;
}
sa->sockets = new_sockets;
sa->fd_to_index = new_fd_to_index;
sa->index_to_fd = new_index_to_fd;
sa->capacity = new_capacity;
}
// Check if FD already exists
if (sa->fd_to_index[fd] != -1) return -1; // FD already exists
// Find first free slot
int index = -1;
for (int i = 0; i < sa->capacity; i++) {
if (!sa->sockets[i].active) {
index = i;
break;
}
}
if (index == -1) return -1; // No free slots (shouldn't happen)
// Add the socket
sa->sockets[index].fd = fd;
sa->sockets[index].read_cbk = read_cbk;
sa->sockets[index].write_cbk = write_cbk;
sa->sockets[index].except_cbk = except_cbk;
sa->sockets[index].user_data = user_data;
sa->sockets[index].active = 1;
sa->fd_to_index[fd] = index;
sa->index_to_fd[index] = fd;
sa->count++;
if (fd > sa->max_fd) sa->max_fd = fd;
return index;
}
static int socket_array_remove(struct socket_array* sa, int fd) {
if (!sa || fd < 0 || fd >= sa->capacity) return -1;
int index = sa->fd_to_index[fd];
if (index == -1 || !sa->sockets[index].active) return -1; // FD not found
// Mark as inactive
sa->sockets[index].active = 0;
sa->sockets[index].fd = -1;
sa->fd_to_index[fd] = -1;
sa->index_to_fd[index] = -1;
sa->count--;
return 0;
}
static struct socket_node* socket_array_get(struct socket_array* sa, int fd) {
if (!sa || fd < 0 || fd >= sa->capacity) return NULL;
int index = sa->fd_to_index[fd];
if (index == -1 || !sa->sockets[index].active) return NULL;
return &sa->sockets[index];
}
// Callback to free timeout node and update counters
static void timeout_node_free_callback(void* user_data, void* data) {
struct UASYNC* ua = (struct UASYNC*)user_data;
if (!data) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "timeout_node_free_callback: NULL data pointer");
return;
}
struct timeout_node* node = (struct timeout_node*)data;
// Проверка на двойное освобождение - смотрим если указатель валидный
if (node) {
DEBUG_TRACE(DEBUG_CATEGORY_TIMERS, "timeout_node_free_callback: freeing node %p", node);
ua->timer_free_count++;
free(data);
} else {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "timeout_node_free_callback: invalid node pointer %p", node);
}
}
// Helper to get current time
static void get_current_time(struct timeval* tv) {
gettimeofday(tv, NULL);
}
// Drain wakeup pipe - read all available bytes
static void drain_wakeup_pipe(struct UASYNC* ua) {
if (!ua || !ua->wakeup_initialized) return;
char buf[64];
while (1) {
ssize_t n = read(ua->wakeup_pipe[0], buf, sizeof(buf));
if (n <= 0) break;
}
}
// Helper to add timeval: tv += dt (timebase units)
static void timeval_add_tb(struct timeval* tv, int dt) {
tv->tv_usec += (dt % 10000) * 100;
tv->tv_sec += dt / 10000 + tv->tv_usec / 1000000;
tv->tv_usec %= 1000000;
}
// Convert timeval to milliseconds (uint64_t)
static uint64_t timeval_to_ms(const struct timeval* tv) {
return (uint64_t)tv->tv_sec * 1000ULL + (uint64_t)tv->tv_usec / 1000ULL;
}
// Simplified timeout handling without reference counting
// Process expired timeouts with safe cancellation
static void process_timeouts(struct UASYNC* ua) {
if (!ua || !ua->timeout_heap) return;
struct timeval now_tv;
get_current_time(&now_tv);
uint64_t now_ms = timeval_to_ms(&now_tv);
while (1) {
TimeoutEntry entry;
if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) break;
if (entry.expiration > now_ms) break;
// Pop the expired timeout
timeout_heap_pop(ua->timeout_heap, &entry);
struct timeout_node* node = (struct timeout_node*)entry.data;
if (node && node->callback && !node->cancelled) {
// Execute callback only if not cancelled
node->callback(node->arg);
}
// Always free the node after processing
if (node && node->ua) {
node->ua->timer_free_count++;
}
free(node);
}
}
// Compute time to next timeout
static void get_next_timeout(struct UASYNC* ua, struct timeval* tv) {
if (!ua || !ua->timeout_heap) {
tv->tv_sec = 0;
tv->tv_usec = 0;
return;
}
TimeoutEntry entry;
if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) {
tv->tv_sec = 0;
tv->tv_usec = 0;
return;
}
struct timeval now_tv;
get_current_time(&now_tv);
uint64_t now_ms = timeval_to_ms(&now_tv);
if (entry.expiration <= now_ms) {
tv->tv_sec = 0;
tv->tv_usec = 0;
return;
}
uint64_t delta_ms = entry.expiration - now_ms;
tv->tv_sec = delta_ms / 1000;
tv->tv_usec = (delta_ms % 1000) * 1000;
}
// Instance version
void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* arg, timeout_callback_t callback) {
if (!ua || timeout_tb < 0 || !callback) return NULL;
if (!ua->timeout_heap) return NULL;
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: ua=%p, timeout=%d tb, arg=%p, callback=%p",
ua, timeout_tb, arg, callback);
struct timeout_node* node = malloc(sizeof(struct timeout_node));
if (!node) {
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to allocate node");
return NULL;
}
ua->timer_alloc_count++;
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: allocated node %p (alloc_count=%zu)",
node, ua->timer_alloc_count);
node->arg = arg;
node->callback = callback;
node->ua = ua;
node->cancelled = 0;
// Calculate expiration time in milliseconds
struct timeval now;
get_current_time(&now);
timeval_add_tb(&now, timeout_tb);
node->expiration_ms = timeval_to_ms(&now);
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: node %p expires at %llu ms",
node, (unsigned long long)node->expiration_ms);
// Add to heap
if (timeout_heap_push(ua->timeout_heap, node->expiration_ms, node) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to push to heap");
free(node);
ua->timer_free_count++; // Balance the alloc counter
return NULL;
}
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: successfully created timer %p", node);
return node;
}
// Instance version
err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id) {
if (!ua || !t_id || !ua->timeout_heap) {
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: invalid parameters ua=%p, t_id=%p, heap=%p",
ua, t_id, ua ? ua->timeout_heap : NULL);
return ERR_FAIL;
}
struct timeout_node* node = (struct timeout_node*)t_id;
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: ua=%p, t_id=%p, node=%p, expires=%llu ms",
ua, t_id, node, (unsigned long long)node->expiration_ms);
// Try to cancel from heap first
if (timeout_heap_cancel(ua->timeout_heap, node->expiration_ms, node) == 0) {
// Successfully removed from heap - mark as cancelled and update counter
node->cancelled = 1;
node->callback = NULL;
ua->timer_free_count++; // Update counter for cancelled timer
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: successfully cancelled timer %p from heap, free_count=%zu", node, ua->timer_free_count);
free(node); // Free the cancelled timer node
return ERR_OK;
}
// If not found in heap, it may have already expired or been invalid
return ERR_FAIL;
}
// Instance version
void* uasync_add_socket(struct UASYNC* ua, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data) {
if (!ua || fd < 0 || fd >= FD_SETSIZE) return NULL; // Bounds check
int index = socket_array_add(ua->sockets, fd, read_cbk, write_cbk, except_cbk, user_data);
if (index < 0) return NULL;
ua->socket_alloc_count++;
// Return pointer to the socket node (same as before for API compatibility)
return &ua->sockets->sockets[index];
}
// Instance version
err_t uasync_remove_socket(struct UASYNC* ua, void* s_id) {
if (!ua || !s_id) return ERR_FAIL;
struct socket_node* node = (struct socket_node*)s_id;
if (node->fd < 0) return ERR_FAIL; // Invalid node
int result = socket_array_remove(ua->sockets, node->fd);
if (result != 0) return ERR_FAIL;
ua->socket_free_count++;
return ERR_OK;
}
void uasync_mainloop(struct UASYNC* ua) {
while (1) {
uasync_poll(ua, -1); /* infinite timeout */
}
}
// Instance version
void uasync_poll(struct UASYNC* ua, int timeout_tb) {
if (!ua) return;
/* Process expired timeouts */
process_timeouts(ua);
/* Compute timeout for poll in milliseconds */
int timeout_ms = -1; // infinite by default
// Get next timeout from heap
struct timeval tv;
get_next_timeout(ua, &tv);
if (tv.tv_sec > 0 || tv.tv_usec > 0 || (ua->timeout_heap && ua->timeout_heap->size > 0)) {
// Convert timeval to milliseconds, cap at INT_MAX
uint64_t ms = (uint64_t)tv.tv_sec * 1000ULL + (uint64_t)tv.tv_usec / 1000ULL;
if (ms > INT_MAX) ms = INT_MAX;
timeout_ms = (int)ms;
}
/* If timeout_tb >= 0, compute timeout as min(timeout_tb, existing timer) */
if (timeout_tb >= 0) {
// Convert timebase (0.1 ms) to milliseconds
int user_timeout_ms = timeout_tb / 10;
if (timeout_tb % 10 != 0) user_timeout_ms++; // round up
if (timeout_ms < 0 || user_timeout_ms < timeout_ms) {
timeout_ms = user_timeout_ms;
}
}
/* Build pollfd array from socket array - O(1) per socket */
int socket_count = ua->sockets ? ua->sockets->count : 0;
int wakeup_fd_present = ua->wakeup_initialized ? 1 : 0;
int total_fds = socket_count + wakeup_fd_present;
if (total_fds == 0) {
/* No sockets and no wakeup fd, just wait for timeout */
if (timeout_ms >= 0) {
/* usleep would be better but we just call poll with empty set */
poll(NULL, 0, timeout_ms);
} else {
/* Infinite timeout with no sockets - should not happen in practice */
return;
}
/* Check timeouts again after sleep */
process_timeouts(ua);
return;
}
struct pollfd* fds = malloc(total_fds * sizeof(struct pollfd));
struct socket_node** nodes = NULL;
if (socket_count > 0) {
nodes = malloc(socket_count * sizeof(struct socket_node*));
}
if (!fds || (socket_count > 0 && !nodes)) {
free(fds);
free(nodes);
return; /* out of memory */
}
/* Fill arrays */
int idx = 0;
/* Add wakeup fd first if present */
if (wakeup_fd_present) {
fds[idx].fd = ua->wakeup_pipe[0];
fds[idx].events = POLLIN;
fds[idx].revents = 0;
idx++;
}
/* Add socket fds using efficient array traversal */
int node_idx = 0;
for (int i = 0; i < ua->sockets->capacity && node_idx < socket_count; i++) {
if (ua->sockets->sockets[i].active) {
struct socket_node* cur = &ua->sockets->sockets[i];
fds[idx].fd = cur->fd;
fds[idx].events = 0;
fds[idx].revents = 0;
if (cur->read_cbk) fds[idx].events |= POLLIN;
if (cur->write_cbk) fds[idx].events |= POLLOUT;
if (cur->except_cbk) fds[idx].events |= POLLPRI;
if (nodes) {
nodes[node_idx] = cur;
}
idx++;
node_idx++;
}
}
/* Call poll */
int ret = poll(fds, total_fds, timeout_ms);
if (ret < 0) {
if (errno == EINTR) {
free(fds);
free(nodes);
return;
}
perror("poll");
free(fds);
free(nodes);
return;
}
/* Process timeouts that may have expired during poll */
process_timeouts(ua);
/* Process socket events */
if (ret > 0) {
for (int i = 0; i < total_fds; i++) {
if (fds[i].revents == 0) continue;
/* Handle wakeup fd separately */
if (wakeup_fd_present && i == 0) {
if (fds[i].revents & POLLIN) {
drain_wakeup_pipe(ua);
}
continue;
}
/* Socket event */
int socket_idx = i - wakeup_fd_present;
struct socket_node* node = nodes[socket_idx];
/* Check for error conditions first */
if (fds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
/* Treat as exceptional condition */
if (node->except_cbk) {
node->except_cbk(node->fd, node->user_data);
}
}
/* Exceptional data (out-of-band) */
if (fds[i].revents & POLLPRI) {
if (node->except_cbk) {
node->except_cbk(node->fd, node->user_data);
}
}
/* Read readiness */
if (fds[i].revents & POLLIN) {
if (node->read_cbk) {
node->read_cbk(node->fd, node->user_data);
}
}
/* Write readiness */
if (fds[i].revents & POLLOUT) {
if (node->write_cbk) {
node->write_cbk(node->fd, node->user_data);
}
}
}
}
free(fds);
free(nodes);
}
// ========== Instance management functions ==========
struct UASYNC* uasync_create(void) {
// Initialize debug system on first use
static int debug_initialized = 0;
if (!debug_initialized) {
debug_config_init();
debug_initialized = 1;
}
struct UASYNC* ua = malloc(sizeof(struct UASYNC));
if (!ua) return NULL;
memset(ua, 0, sizeof(struct UASYNC));
ua->wakeup_pipe[0] = -1;
ua->wakeup_pipe[1] = -1;
ua->wakeup_initialized = 0;
// Create wakeup pipe
if (pipe(ua->wakeup_pipe) < 0) {
DEBUG_WARN(DEBUG_CATEGORY_UASYNC, "Failed to create wakeup pipe: %s", strerror(errno));
// Continue without wakeup mechanism
ua->wakeup_pipe[0] = -1;
ua->wakeup_pipe[1] = -1;
} else {
ua->wakeup_initialized = 1;
// Set non-blocking on read end to avoid blocking if pipe is full
int flags = fcntl(ua->wakeup_pipe[0], F_GETFL, 0);
if (flags >= 0) {
fcntl(ua->wakeup_pipe[0], F_SETFL, flags | O_NONBLOCK);
}
}
ua->sockets = socket_array_create(16);
if (!ua->sockets) {
if (ua->wakeup_initialized) {
close(ua->wakeup_pipe[0]);
close(ua->wakeup_pipe[1]);
}
free(ua);
return NULL;
}
ua->timeout_heap = timeout_heap_create(16);
if (!ua->timeout_heap) {
socket_array_destroy(ua->sockets);
if (ua->wakeup_initialized) {
close(ua->wakeup_pipe[0]);
close(ua->wakeup_pipe[1]);
}
free(ua);
return NULL;
}
// Set callback to free timeout nodes and update counters
timeout_heap_set_free_callback(ua->timeout_heap, ua, timeout_node_free_callback);
return ua;
}
// Print all resources for debugging
void uasync_print_resources(struct UASYNC* ua, const char* prefix) {
if (!ua) {
printf("%s: NULL uasync instance\n", prefix);
return;
}
printf("\n🔍 %s: UASYNC Resource Report for %p\n", prefix, ua);
printf(" Timer Statistics: allocated=%zu, freed=%zu, active=%zd\n",
ua->timer_alloc_count, ua->timer_free_count,
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count));
printf(" Socket Statistics: allocated=%zu, freed=%zu, active=%zd\n",
ua->socket_alloc_count, ua->socket_free_count,
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count));
// Показать активные таймеры
if (ua->timeout_heap) {
size_t active_timers = 0;
// Безопасное чтение без извлечения - просто итерируем по массиву
for (size_t i = 0; i < ua->timeout_heap->size; i++) {
if (!ua->timeout_heap->heap[i].deleted) {
active_timers++;
struct timeout_node* node = (struct timeout_node*)ua->timeout_heap->heap[i].data;
printf(" Timer: node=%p, expires=%llu ms, cancelled=%d\n",
node, (unsigned long long)ua->timeout_heap->heap[i].expiration, node->cancelled);
}
}
printf(" Active timers in heap: %zu\n", active_timers);
}
// Показать активные сокеты
if (ua->sockets) {
int active_sockets = 0;
printf(" Socket array capacity: %d, active: %d\n",
ua->sockets->capacity, ua->sockets->count);
for (int i = 0; i < ua->sockets->capacity; i++) {
if (ua->sockets->sockets[i].active) {
active_sockets++;
printf(" Socket: fd=%d, active=%d\n",
ua->sockets->sockets[i].fd,
ua->sockets->sockets[i].active);
}
}
printf(" Total active sockets: %d\n", active_sockets);
}
printf("🔚 %s: End of resource report\n\n", prefix);
}
void uasync_destroy(struct UASYNC* ua, int close_fds) {
if (!ua) return;
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: starting cleanup for ua=%p", ua);
// Диагностика ресурсов перед очисткой (отключена для избежания double free)
// uasync_print_resources(ua, "BEFORE_DESTROY");
// Check for potential memory leaks
if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory leaks detected before cleanup: timers %zu/%zu, sockets %zu/%zu",
ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count);
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Timer leak: allocated=%zu, freed=%zu, diff=%zd",
ua->timer_alloc_count, ua->timer_free_count,
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count));
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Socket leak: allocated=%zu, freed=%zu, diff=%zd",
ua->socket_alloc_count, ua->socket_free_count,
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count));
// Continue cleanup, will abort after if leaks remain
}
// Free all remaining timeouts (временно полностью отключено для диагностики double free)
if (ua->timeout_heap) {
DEBUG_WARN(DEBUG_CATEGORY_MEMORY, "uasync_destroy: timer cleanup completely disabled due to double free in timeout_heap_pop()");
// Просто уничтожаем кучу без очистки элементов
timeout_heap_destroy(ua->timeout_heap);
ua->timeout_heap = NULL;
ua->timer_free_count = ua->timer_alloc_count; // Предполагаем все освобождено
}
// Free all socket nodes using array approach
if (ua->sockets) {
// Count and free all active sockets
int freed_count = 0;
for (int i = 0; i < ua->sockets->capacity; i++) {
if (ua->sockets->sockets[i].active) {
if (close_fds && ua->sockets->sockets[i].fd >= 0) {
close(ua->sockets->sockets[i].fd);
}
ua->socket_free_count++;
freed_count++;
}
}
DEBUG_DEBUG(DEBUG_CATEGORY_MEMORY, "Freed %d socket nodes in destroy", freed_count);
socket_array_destroy(ua->sockets);
}
// Close wakeup pipe
if (ua->wakeup_initialized) {
close(ua->wakeup_pipe[0]);
close(ua->wakeup_pipe[1]);
}
// Final leak check
if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory leaks detected after cleanup: timers %zu/%zu, sockets %zu/%zu",
ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count);
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "FINAL Timer leak: allocated=%zu, freed=%zu, diff=%zd",
ua->timer_alloc_count, ua->timer_free_count,
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count));
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "FINAL Socket leak: allocated=%zu, freed=%zu, diff=%zd",
ua->socket_alloc_count, ua->socket_free_count,
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count));
abort();
}
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: completed successfully for ua=%p", ua);
free(ua);
}
void uasync_init_instance(struct UASYNC* ua) {
if (!ua) return;
// Initialize socket array if not present
if (!ua->sockets) {
ua->sockets = socket_array_create(16);
}
if (!ua->timeout_heap) {
ua->timeout_heap = timeout_heap_create(16);
if (ua->timeout_heap) {
timeout_heap_set_free_callback(ua->timeout_heap, ua, timeout_node_free_callback);
}
}
}
// Debug statistics
void uasync_get_stats(struct UASYNC* ua, size_t* timer_alloc, size_t* timer_free, size_t* socket_alloc, size_t* socket_free) {
if (!ua) return;
if (timer_alloc) *timer_alloc = ua->timer_alloc_count;
if (timer_free) *timer_free = ua->timer_free_count;
if (socket_alloc) *socket_alloc = ua->socket_alloc_count;
if (socket_free) *socket_free = ua->socket_free_count;
}
// Get global instance for backward compatibility
// Wakeup mechanism
int uasync_wakeup(struct UASYNC* ua) {
if (!ua || !ua->wakeup_initialized) return -1;
char byte = 0;
ssize_t ret = write(ua->wakeup_pipe[1], &byte, 1);
if (ret != 1) {
// Don't print error from signal handler
return -1;
}
return 0;
}
int uasync_get_wakeup_fd(struct UASYNC* ua) {
if (!ua || !ua->wakeup_initialized) return -1;
return ua->wakeup_pipe[1];
}

617
lib/u_async.c3

@ -1,617 +0,0 @@
// uasync.c
#include "u_async.h"
#include "debug_config.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <poll.h>
#include <limits.h>
#include <fcntl.h>
#include <sys/time.h>
// Socket node with array-based storage
struct socket_node {
int fd;
socket_callback_t read_cbk;
socket_callback_t write_cbk;
socket_callback_t except_cbk;
void* user_data;
int active; // 1 if socket is active, 0 if freed (for reuse)
};
// Array-based socket management for O(1) operations
struct socket_array {
struct socket_node* sockets; // Dynamic array of socket nodes
int* fd_to_index; // FD to array index mapping
int* index_to_fd; // Array index to FD mapping
int capacity; // Total allocated capacity
int count; // Number of active sockets
int max_fd; // Maximum FD for bounds checking
};
static struct socket_array* socket_array_create(int initial_capacity);
static void socket_array_destroy(struct socket_array* sa);
static int socket_array_add(struct socket_array* sa, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data);
static int socket_array_remove(struct socket_array* sa, int fd);
static struct socket_node* socket_array_get(struct socket_array* sa, int fd);
// Helper to get current time
static void get_current_time(struct timeval* tv) {
gettimeofday(tv, NULL);
}
static void timeval_add_tb(struct timeval* tv, int dt) {
if (dt <= 0) return;
long long us_add = (long long)dt * 100LL; // 0.1ms = 100us
tv->tv_usec += us_add % 1000000LL;
tv->tv_sec += us_add / 1000000LL;
if (tv->tv_usec >= 1000000LL) {
tv->tv_sec += tv->tv_usec / 1000000LL;
tv->tv_usec %= 1000000LL;
}
}
static uint64_t timeval_to_ms(const struct timeval* tv) {
return (uint64_t)tv->tv_sec * 1000ULL + (uint64_t)tv->tv_usec / 1000ULL;
}
static void drain_wakeup_pipe(struct UASYNC* ua) {
char buf[1024];
while (read(ua->wakeup_pipe[0], buf, sizeof(buf)) > 0);
}
// Process expired timeouts with safe cancellation
static void process_timeouts(struct UASYNC* ua) {
if (!ua || !ua->timeout_heap) return;
struct timeval now_tv;
get_current_time(&now_tv);
uint64_t now_ms = timeval_to_ms(&now_tv);
while (1) {
TimeoutEntry entry;
if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) break;
if (entry.expiration > now_ms) break;
// Pop the expired timeout
timeout_heap_pop(ua->timeout_heap, &entry);
struct timeout_node* node = (struct timeout_node*)entry.data;
if (node && node->callback && !node->cancelled) {
// Execute callback only if not cancelled
node->callback(node->arg);
}
// Always free the node after processing
if (node && node->ua) {
node->ua->timer_free_count++;
}
free(node);
}
}
// Instance version
void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* arg, timeout_callback_t callback) {
if (!ua || timeout_tb < 0 || !callback) return NULL;
if (!ua->timeout_heap) return NULL;
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: ua=%p, timeout=%d tb, arg=%p, callback=%p",
ua, timeout_tb, arg, callback);
struct timeout_node* node = malloc(sizeof(struct timeout_node));
if (!node) {
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to allocate node");
return NULL;
}
ua->timer_alloc_count++;
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: allocated node %p (alloc_count=%zu)",
node, ua->timer_alloc_count);
node->arg = arg;
node->callback = callback;
node->ua = ua;
node->cancelled = 0;
node->heap_index = SIZE_MAX; // Initialize
// Calculate expiration time in milliseconds
struct timeval now;
get_current_time(&now);
timeval_add_tb(&now, timeout_tb);
node->expiration_ms = timeval_to_ms(&now);
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: node %p expires at %llu ms",
node, (unsigned long long)node->expiration_ms);
// Add to heap
if (timeout_heap_push(ua->timeout_heap, node->expiration_ms, node) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to push to heap");
free(node);
ua->timer_free_count++; // Balance the alloc counter
return NULL;
}
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: successfully created timer %p", node);
return node;
}
// Instance version
err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id) {
if (!ua || !t_id || !ua->timeout_heap) {
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: invalid parameters ua=%p, t_id=%p, heap=%p",
ua, t_id, ua ? ua->timeout_heap : NULL);
return ERR_FAIL;
}
struct timeout_node* node = (struct timeout_node*)t_id;
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: ua=%p, t_id=%p, node=%p, expires=%llu ms",
ua, t_id, node, (unsigned long long)node->expiration_ms);
// Try to remove from heap
if (timeout_heap_remove(ua->timeout_heap, node) == 0) {
// Successfully removed - free it
node->cancelled = 1;
node->callback = NULL;
ua->timer_free_count++;
free(node);
return ERR_OK;
} else {
// Not found in heap (may be already popped) - just mark cancelled to skip callback if pending
node->cancelled = 1;
node->callback = NULL;
// Do NOT free here to avoid double-free if already processed
return ERR_OK;
}
}
void* uasync_add_socket(struct UASYNC* ua, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data) {
if (!ua || !ua->sockets) return NULL;
if (socket_array_add(ua->sockets, fd, read_cbk, write_cbk, except_cbk, user_data) != 0) return NULL;
ua->socket_alloc_count++;
return socket_array_get(ua->sockets, fd);
}
err_t uasync_remove_socket(struct UASYNC* ua, void* s_id) {
if (!ua || !ua->sockets || !s_id) return ERR_FAIL;
struct socket_node* node = (struct socket_node*)s_id;
if (socket_array_remove(ua->sockets, node->fd) != 0) return ERR_FAIL;
ua->socket_free_count++;
return ERR_OK;
}
void uasync_poll(struct UASYNC* ua, int timeout_tb) {
if (!ua) return;
// Get next timeout
int timeout_ms = -1;
if (ua->timeout_heap && ua->timeout_heap->size > 0) {
struct timeval now_tv;
get_current_time(&now_tv);
uint64_t now_ms = timeval_to_ms(&now_tv);
TimeoutEntry entry;
if (timeout_heap_peek(ua->timeout_heap, &entry) == 0) {
if (entry.expiration > now_ms) {
timeout_ms = (int)(entry.expiration - now_ms);
} else {
timeout_ms = 0;
}
}
}
if (timeout_tb >= 0) {
int tb_ms = timeout_tb / 10; // approx tb to ms (adjust if tb unit is different)
if (timeout_ms < 0 || tb_ms < timeout_ms) timeout_ms = tb_ms;
}
// Prepare poll fds
int wakeup_fd_present = ua->wakeup_initialized && ua->wakeup_pipe[0] >= 0;
int socket_count = ua->sockets ? ua->sockets->count : 0;
int total_fds = socket_count + wakeup_fd_present;
struct pollfd* fds = malloc(total_fds * sizeof(struct pollfd));
struct socket_node** nodes = socket_count > 0 ? malloc(socket_count * sizeof(struct socket_node*)) : NULL;
if (!fds || (socket_count > 0 && !nodes)) {
free(fds);
free(nodes);
return; /* out of memory */
}
/* Fill arrays */
int idx = 0;
/* Add wakeup fd first if present */
if (wakeup_fd_present) {
fds[idx].fd = ua->wakeup_pipe[0];
fds[idx].events = POLLIN;
fds[idx].revents = 0;
idx++;
}
/* Add socket fds using efficient array traversal */
int node_idx = 0;
for (int i = 0; i < ua->sockets->capacity && node_idx < socket_count; i++) {
if (ua->sockets->sockets[i].active) {
struct socket_node* cur = &ua->sockets->sockets[i];
fds[idx].fd = cur->fd;
fds[idx].events = 0;
fds[idx].revents = 0;
if (cur->read_cbk) fds[idx].events |= POLLIN;
if (cur->write_cbk) fds[idx].events |= POLLOUT;
if (cur->except_cbk) fds[idx].events |= POLLPRI;
if (nodes) {
nodes[node_idx] = cur;
}
idx++;
node_idx++;
}
}
/* Call poll */
int ret = poll(fds, total_fds, timeout_ms);
if (ret < 0) {
if (errno == EINTR) {
free(fds);
free(nodes);
return;
}
perror("poll");
free(fds);
free(nodes);
return;
}
/* Process timeouts that may have expired during poll */
process_timeouts(ua);
/* Process socket events */
if (ret > 0) {
for (int i = 0; i < total_fds; i++) {
if (fds[i].revents == 0) continue;
/* Handle wakeup fd separately */
if (wakeup_fd_present && i == 0) {
if (fds[i].revents & POLLIN) {
drain_wakeup_pipe(ua);
}
continue;
}
/* Socket event */
int socket_idx = i - wakeup_fd_present;
struct socket_node* node = nodes[socket_idx];
/* Check for error conditions first */
if (fds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
/* Treat as exceptional condition */
if (node->except_cbk) {
node->except_cbk(node->fd, node->user_data);
}
}
/* Exceptional data (out-of-band) */
if (fds[i].revents & POLLPRI) {
if (node->except_cbk) {
node->except_cbk(node->fd, node->user_data);
}
}
/* Read readiness */
if (fds[i].revents & POLLIN) {
if (node->read_cbk) {
node->read_cbk(node->fd, node->user_data);
}
}
/* Write readiness */
if (fds[i].revents & POLLOUT) {
if (node->write_cbk) {
node->write_cbk(node->fd, node->user_data);
}
}
}
}
free(fds);
free(nodes);
}
void uasync_mainloop(struct UASYNC* ua) {
while (1) {
uasync_poll(ua, -1);
}
}
struct UASYNC* uasync_create(void) {
// Initialize debug system on first use
static int debug_initialized = 0;
if (!debug_initialized) {
debug_config_init();
debug_initialized = 1;
}
struct UASYNC* ua = malloc(sizeof(struct UASYNC));
if (!ua) return NULL;
memset(ua, 0, sizeof(struct UASYNC));
ua->wakeup_pipe[0] = -1;
ua->wakeup_pipe[1] = -1;
ua->wakeup_initialized = 0;
// Create wakeup pipe
if (pipe(ua->wakeup_pipe) < 0) {
DEBUG_WARN(DEBUG_CATEGORY_UASYNC, "Failed to create wakeup pipe: %s", strerror(errno));
// Continue without wakeup mechanism
ua->wakeup_pipe[0] = -1;
ua->wakeup_pipe[1] = -1;
} else {
ua->wakeup_initialized = 1;
// Set non-blocking on read end to avoid blocking if pipe is full
int flags = fcntl(ua->wakeup_pipe[0], F_GETFL, 0);
if (flags >= 0) {
fcntl(ua->wakeup_pipe[0], F_SETFL, flags | O_NONBLOCK);
}
}
ua->sockets = socket_array_create(16);
if (!ua->sockets) {
if (ua->wakeup_initialized) {
close(ua->wakeup_pipe[0]);
close(ua->wakeup_pipe[1]);
}
free(ua);
return NULL;
}
ua->timeout_heap = timeout_heap_create(16);
if (!ua->timeout_heap) {
socket_array_destroy(ua->sockets);
if (ua->wakeup_initialized) {
close(ua->wakeup_pipe[0]);
close(ua->wakeup_pipe[1]);
}
free(ua);
return NULL;
}
return ua;
}
// Print all resources for debugging
void uasync_print_resources(struct UASYNC* ua, const char* prefix) {
if (!ua) {
printf("%s: NULL uasync instance\n", prefix);
return;
}
printf("\n🔍 %s: UASYNC Resource Report for %p\n", prefix, ua);
printf(" Timer Statistics: allocated=%zu, freed=%zu, active=%zd\n",
ua->timer_alloc_count, ua->timer_free_count,
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count));
printf(" Socket Statistics: allocated=%zu, freed=%zu, active=%zd\n",
ua->socket_alloc_count, ua->socket_free_count,
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count));
// Show active timers (removed check for .deleted; heap entries are valid up to .size)
if (ua->timeout_heap) {
size_t active_timers = ua->timeout_heap->size;
for (size_t i = 0; i < ua->timeout_heap->size; i++) {
struct timeout_node* node = (struct timeout_node*)ua->timeout_heap->heap[i].data;
printf(" Timer: node=%p, expires=%llu ms, cancelled=%d\n",
node, (unsigned long long)ua->timeout_heap->heap[i].expiration, node->cancelled);
}
printf(" Active timers in heap: %zu\n", active_timers);
}
// Show active sockets
if (ua->sockets) {
int active_sockets = 0;
printf(" Socket array capacity: %d, active: %d\n",
ua->sockets->capacity, ua->sockets->count);
for (int i = 0; i < ua->sockets->capacity; i++) {
if (ua->sockets->sockets[i].active) {
active_sockets++;
printf(" Socket: fd=%d, active=%d\n",
ua->sockets->sockets[i].fd,
ua->sockets->sockets[i].active);
}
}
printf(" Total active sockets: %d\n", active_sockets);
}
printf("🔚 %s: End of resource report\n\n", prefix);
}
void uasync_destroy(struct UASYNC* ua, int close_fds) {
if (!ua) return;
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: starting cleanup for ua=%p", ua);
// Free all remaining timeouts by popping and freeing data (fixes leak without double-free)
if (ua->timeout_heap) {
TimeoutEntry entry;
while (timeout_heap_pop(ua->timeout_heap, &entry) == 0) {
struct timeout_node* node = (struct timeout_node*)entry.data;
if (node) {
ua->timer_free_count++;
free(node);
}
}
timeout_heap_destroy(ua->timeout_heap);
ua->timeout_heap = NULL;
}
// Free all socket nodes using array approach
if (ua->sockets) {
// Count and free all active sockets
int freed_count = 0;
for (int i = 0; i < ua->sockets->capacity; i++) {
if (ua->sockets->sockets[i].active) {
if (close_fds && ua->sockets->sockets[i].fd >= 0) {
close(ua->sockets->sockets[i].fd);
}
ua->socket_free_count++;
freed_count++;
}
}
DEBUG_DEBUG(DEBUG_CATEGORY_MEMORY, "Freed %d socket nodes in destroy", freed_count);
socket_array_destroy(ua->sockets);
}
// Close wakeup pipe
if (ua->wakeup_initialized) {
close(ua->wakeup_pipe[0]);
close(ua->wakeup_pipe[1]);
}
// Final leak check
if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory leaks detected after cleanup: timers %zu/%zu, sockets %zu/%zu",
ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count);
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "FINAL Timer leak: allocated=%zu, freed=%zu, diff=%zd",
ua->timer_alloc_count, ua->timer_free_count,
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count));
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "FINAL Socket leak: allocated=%zu, freed=%zu, diff=%zd",
ua->socket_alloc_count, ua->socket_free_count,
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count));
abort();
}
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: completed successfully for ua=%p", ua);
free(ua);
}
void uasync_init_instance(struct UASYNC* ua) {
if (!ua) return;
// Initialize socket array if not present
if (!ua->sockets) {
ua->sockets = socket_array_create(16);
}
if (!ua->timeout_heap) {
ua->timeout_heap = timeout_heap_create(16);
}
}
// Debug statistics
void uasync_get_stats(struct UASYNC* ua, size_t* timer_alloc, size_t* timer_free, size_t* socket_alloc, size_t* socket_free) {
if (!ua) return;
if (timer_alloc) *timer_alloc = ua->timer_alloc_count;
if (timer_free) *timer_free = ua->timer_free_count;
if (socket_alloc) *socket_alloc = ua->socket_alloc_count;
if (socket_free) *socket_free = ua->socket_free_count;
}
// Wakeup mechanism
int uasync_wakeup(struct UASYNC* ua) {
if (!ua || !ua->wakeup_initialized) return -1;
char byte = 0;
ssize_t ret = write(ua->wakeup_pipe[1], &byte, 1);
if (ret != 1) {
// Don't print error from signal handler
return -1;
}
return 0;
}
int uasync_get_wakeup_fd(struct UASYNC* ua) {
if (!ua || !ua->wakeup_initialized) return -1;
return ua->wakeup_pipe[1];
}
static struct socket_array* socket_array_create(int initial_capacity) {
struct socket_array* sa = malloc(sizeof(struct socket_array));
if (!sa) return NULL;
sa->sockets = malloc(initial_capacity * sizeof(struct socket_node));
if (!sa->sockets) {
free(sa);
return NULL;
}
sa->fd_to_index = malloc(FD_SETSIZE * sizeof(int)); // Assume FD_SETSIZE defined
if (!sa->fd_to_index) {
free(sa->sockets);
free(sa);
return NULL;
}
memset(sa->fd_to_index, -1, FD_SETSIZE * sizeof(int));
sa->index_to_fd = malloc(initial_capacity * sizeof(int));
if (!sa->index_to_fd) {
free(sa->fd_to_index);
free(sa->sockets);
free(sa);
return NULL;
}
sa->capacity = initial_capacity;
sa->count = 0;
sa->max_fd = 0;
return sa;
}
static void socket_array_destroy(struct socket_array* sa) {
if (sa) {
free(sa->sockets);
free(sa->fd_to_index);
free(sa->index_to_fd);
free(sa);
}
}
static int socket_array_add(struct socket_array* sa, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data) {
if (fd < 0 || fd >= FD_SETSIZE) return -1;
if (sa->fd_to_index[fd] != -1) return -1; // Already exists
if (sa->count == sa->capacity) {
int new_cap = sa->capacity * 2;
struct socket_node* new_sockets = realloc(sa->sockets, new_cap * sizeof(struct socket_node));
if (!new_sockets) return -1;
sa->sockets = new_sockets;
int* new_index_to_fd = realloc(sa->index_to_fd, new_cap * sizeof(int));
if (!new_index_to_fd) return -1;
sa->index_to_fd = new_index_to_fd;
sa->capacity = new_cap;
}
int index = sa->count++;
sa->sockets[index].fd = fd;
sa->sockets[index].read_cbk = read_cbk;
sa->sockets[index].write_cbk = write_cbk;
sa->sockets[index].except_cbk = except_cbk;
sa->sockets[index].user_data = user_data;
sa->sockets[index].active = 1;
sa->fd_to_index[fd] = index;
sa->index_to_fd[index] = fd;
if (fd > sa->max_fd) sa->max_fd = fd;
return 0;
}
static int socket_array_remove(struct socket_array* sa, int fd) {
if (fd < 0 || fd >= FD_SETSIZE) return -1;
int index = sa->fd_to_index[fd];
if (index == -1) return -1;
sa->sockets[index].active = 0;
sa->fd_to_index[fd] = -1;
// Move last to hole
int last_index = --sa->count;
if (index != last_index) {
sa->sockets[index] = sa->sockets[last_index];
int last_fd = sa->index_to_fd[last_index];
sa->fd_to_index[last_fd] = index;
sa->index_to_fd[index] = last_fd;
}
return 0;
}
static struct socket_node* socket_array_get(struct socket_array* sa, int fd) {
if (fd < 0 || fd >= FD_SETSIZE) return NULL;
int index = sa->fd_to_index[fd];
if (index == -1) return NULL;
return &sa->sockets[index];
}

69
lib/u_async.h1

@ -1,69 +0,0 @@
// uasync.h
// модуль асинхронных операций. добавляем сокеты и таймауты и mainloop их обслуживает.
#ifndef UASYNC_H
#define UASYNC_H
#include <sys/time.h>
#include <stddef.h>
#include "timeout_heap.h"
typedef void (*timeout_callback_t)(void* user_arg);// передаёт user_arg из uasync_set_timeout
typedef void (*socket_callback_t)(int fd, void* user_arg);// передаёт user_arg из uasync_add_socket
// user_arg полезен если нужно передать управляющую структуру. Ее можно выделить в памяти и в ней хранить всё что надо. т.е. при set_timeout передаём и получаем ее в callback-е
// Error type
typedef int err_t;
#define ERR_OK 0
#define ERR_FAIL -1
// Uasync instance structure
struct UASYNC {
TimeoutHeap* timeout_heap; // Heap for timeout management
struct socket_array* sockets; // Array-based socket management
// Debug counters for memory allocation tracking
size_t timer_alloc_count;
size_t timer_free_count;
size_t socket_alloc_count;
size_t socket_free_count;
// Wakeup pipe for interrupting poll
int wakeup_pipe[2]; // [0] read, [1] write
int wakeup_initialized;
};
// Type definitions
typedef struct UASYNC uasync_t;
typedef struct UASYNC UASYNC_t;
// Instance API - основной API для работы с uasync
struct UASYNC* uasync_create(void);
void uasync_destroy(struct UASYNC* ua);
void uasync_init_instance(struct UASYNC* ua);
// Timeouts, timebase = 0.1 mS
void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* user_arg, timeout_callback_t callback);
err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id);
// Sockets
void* uasync_add_socket(struct UASYNC* ua, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_arg);
err_t uasync_remove_socket(struct UASYNC* ua, void* s_id);
// Single iteration of event loop with timeout (timebase units)
void uasync_poll(struct UASYNC* ua, int timeout_tb);
// Mainloop (бесконечный цикл, __noreturn)
void uasync_mainloop(struct UASYNC* ua);
// Debug statistics
void uasync_get_stats(struct UASYNC* ua, size_t* timer_alloc, size_t* timer_free, size_t* socket_alloc, size_t* socket_free);
// Print all resources (timers, sockets) for debugging
void uasync_print_resources(struct UASYNC* ua, const char* prefix);
// Wakeup mechanism for interrupting poll
int uasync_wakeup(struct UASYNC* ua);
int uasync_get_wakeup_fd(struct UASYNC* ua); // returns write fd for wakeup pipe (for signal handlers)
#endif // UASYNC_H

186
src/etcp.c

@ -3,6 +3,7 @@
#include "etcp.h"
#include "etcp_loadbalancer.h"
#include "../lib/u_async.h"
#include "../lib/ll_queue.h"
#include "../lib/debug_config.h"
#include "crc32.h" // For potential hashing, though not used yet.
#include <stdlib.h>
@ -32,6 +33,7 @@
static void input_queue_cb(struct ll_queue* q, void* arg);
static void etcp_link_ready_callback(struct ETCP_CONN* etcp);
static void input_send_q_cb(struct ll_queue* q, void* arg);
static void wait_ack_cb(struct ll_queue* q, void* arg);
static void etcp_conn_process_send_queue(struct ETCP_CONN* etcp);
// Get current time in 0.1ms units
@ -39,20 +41,20 @@ uint64_t get_current_time_units() {
struct timeval tv;
gettimeofday(&tv, NULL);
uint64_t time_units = ((uint64_t)tv.tv_sec * 10000ULL) + (tv.tv_usec / 100);
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "get_current_time_units: tv_sec=%ld, tv_usec=%ld, result=%llu",
tv.tv_sec, tv.tv_usec, (unsigned long long)time_units);
// DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "get_current_time_units: tv_sec=%ld, tv_usec=%ld, result=%llu",
// tv.tv_sec, tv.tv_usec, (unsigned long long)time_units);
return time_units;
}
uint16_t get_current_timestamp() {
uint16_t timestamp = (uint16_t)(get_current_time_units() & 0xFFFF);
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "get_current_timestamp: result=%u", timestamp);
// DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "get_current_timestamp: result=%u", timestamp);
return timestamp;
}
// Timestamp diff (with wrap-around)
static uint16_t timestamp_diff(uint16_t t1, uint16_t t2) {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "timestamp_diff: t1=%u, t2=%u", t1, t2);
// DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "timestamp_diff: t1=%u, t2=%u", t1, t2);
if (t1 >= t2) {
return t1 - t2;
}
@ -99,9 +101,11 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance) {
// Set input queue callback
queue_set_callback(etcp->input_queue, input_queue_cb, etcp);
queue_set_callback(etcp->input_send_q, input_send_q_cb, etcp);
queue_set_callback(etcp->input_wait_ack, wait_ack_cb, etcp);
etcp->link_ready_for_send_fn = etcp_link_ready_callback;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_create: queues created, ETCP=%p mtu=%d, window_size=%u, next_tx_id=%u",
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_create: connection initialized. ETCP=%p mtu=%d, window_size=%u, next_tx_id=%u",
etcp, etcp->mtu, etcp->window_size, etcp->next_tx_id);
return etcp;
@ -182,7 +186,7 @@ void etcp_conn_reset(struct ETCP_CONN* etcp) {
// Allocates memory from data_pool and places in input queue
// Returns: 0 on success, -1 on failure
int etcp_send(struct ETCP_CONN* etcp, const void* data, uint16_t len) {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_send: ENTER etcp=%p, data=%p, len=%zu", etcp, data, len);
// DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_send: ENTER etcp=%p, data=%p, len=%zu", etcp, data, len);
if (!etcp || !data || len == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_send: invalid parameters (etcp=%p, data=%p, len=%zu)", etcp, data, len);
@ -222,7 +226,7 @@ int etcp_send(struct ETCP_CONN* etcp, const void* data, uint16_t len) {
pkt->seq = 0; // Will be assigned by input_queue_cb
pkt->timestamp = 0; // Will be set by input_queue_cb
pkt->pkt_data = packet_data; // Point to data_pool allocation
pkt->pkt_len = len;
pkt->ll.size = len; // размер packet_data
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_send: created PACKET %p with data %p (len=%zu)", pkt, packet_data, len);
@ -261,7 +265,7 @@ static void input_queue_cb(struct ll_queue* q, void* arg) {
struct ETCP_FRAGMENT* in_pkt = queue_data_get(q);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "input_queue_cb: processing ETCP_FRAGMENT %p (seq=%u, len=%u, data=%p)",
in_pkt, in_pkt->seq, in_pkt->pkt_len, in_pkt->pkt_data);
in_pkt, in_pkt->seq, in_pkt->ll.size, in_pkt->pkt_data);
// Create INFLIGHT_PACKET
struct INFLIGHT_PACKET* p = memory_pool_alloc(etcp->inflight_pool);
@ -274,9 +278,11 @@ static void input_queue_cb(struct ll_queue* q, void* arg) {
// Setup inflight packet (based on protocol.txt)
memset(p, 0, sizeof(*p));
p->pkt = in_pkt; // Note: ETCP_FRAGMENT is now owned by inflight; don't free here
p->seq = etcp->next_tx_id++; // Assign seq
p->state = INFLIGHT_STATE_WAIT_SEND;
p->last_timestamp = 0;
p->pkt_data = in_pkt->pkt_data;
p->ll.size = in_pkt->ll.size;
// Add to send queue
if (queue_data_put(etcp->input_send_q, p, p->seq) != 0) {
@ -292,9 +298,64 @@ static void input_queue_cb(struct ll_queue* q, void* arg) {
static void input_send_q_cb(struct ll_queue* q, void* arg) {// etcp->input_send_q processing
struct ETCP_CONN* etcp=(struct ETCP_CONN*)arg;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "input_send_q_cb: ");
etcp_request_pkt(etcp);
size_t send_q_bytes = queue_total_bytes(etcp->input_send_q);
size_t send_q_pkts = queue_entry_count(etcp->input_send_q);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "input_send_q_cb: input_send_q status: %d pkt %d bytes", send_q_pkts, send_q_bytes);
etcp_conn_process_send_queue(etcp);
}
static void ack_timeout_check(void* arg) {
struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg;
uint64_t now = get_current_time_units();
uint64_t timeout = 1000;//(uint64_t)(etcp->rtt_avg_10 * RETRANS_K1) + (uint64_t)(etcp->jitter * RETRANS_K2);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "ack_timeout_check: starting check, now=%llu, timeout=%llu, rtt_avg_10=%u, jitter=%u",
(unsigned long long)now, (unsigned long long)timeout, etcp->rtt_avg_10, etcp->jitter);
struct ll_entry* current = etcp->input_wait_ack->head;
while (current) {
struct ll_entry* next = current->next; // Save next as we may remove current
struct INFLIGHT_PACKET* pkt = (struct INFLIGHT_PACKET*)current;
uint64_t elapsed = now - pkt->last_timestamp;
if (elapsed > timeout) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "ack_timeout_check: timeout for seq=%u, elapsed=%llu, timeout=%llu, send_count=%u",
pkt->seq, (unsigned long long)elapsed, (unsigned long long)timeout, pkt->send_count);
// Increment counters
pkt->send_count++;
pkt->retrans_req_count++; // Optional, if used for retrans request logic
pkt->last_timestamp = now;
pkt->last_link = NULL; // Reset last link for re-selection
// Remove from wait_ack
queue_remove_data(etcp->input_wait_ack, pkt);
// Change state and add to send_q for retransmission
pkt->state = INFLIGHT_STATE_WAIT_SEND;
queue_data_put(etcp->input_send_q, pkt, pkt->seq);
// Update stats
etcp->retransmissions_count++;
}
else {// не надо до конца сканировать - они уже сортированы по таймстемпу т.к. очередь fifo, а timestamp = время добавления в очередь = время отправки
// shedule timer
int64_t next_timeout=timeout - elapsed;
etcp->retrans_timer = uasync_set_timeout(etcp->instance->ua, next_timeout+10, etcp, ack_timeout_check);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "ack_timeout_check: rescheduled timer for %llu units", next_timeout);
break;
}
current = next;
}
}
static void wait_ack_cb(struct ll_queue* q, void* arg) {
struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg;
ack_timeout_check(etcp);
}
// Подготовить и отправить кодограмму
// вызывается линком когда освобождается или очередью если появляются данные на передачу
@ -303,32 +364,35 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
struct ETCP_LINK* link = etcp_loadbalancer_select_link(etcp);
if (!link) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: no link available");
queue_resume_callback(etcp->input_send_q);
return NULL;
return NULL;// если линков нет - ждём появления свободного
}
size_t send_q_bytes = queue_total_bytes(etcp->input_send_q);
size_t send_q_size = queue_entry_count(etcp->input_send_q);
if (send_q_bytes == 0) {// сгребаем из других мест
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: input_send_q empty, trying to resume");
if (send_q_size == 0) {// сгребаем из input_queue
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: input_send_q empty, check if avail input_queue -> inflight");
input_queue_try_resume(etcp);
return NULL;
}
// First, check if there's a packet in input_send_q (retrans or new)
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: getting packet from input_send_q");
// DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: getting packet from input_send_q");
struct INFLIGHT_PACKET* inf_pkt = queue_data_get(etcp->input_send_q);
if (!inf_pkt) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: no packet available from input_send_q");
if (inf_pkt) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: prepare udp dgram for send packet %p (seq=%u, len=%u)", inf_pkt, inf_pkt->seq, inf_pkt->ll.size);
inf_pkt->last_timestamp=get_current_time_units();
inf_pkt->send_count++;
inf_pkt->state=INFLIGHT_STATE_WAIT_ACK;
queue_data_put(etcp->input_wait_ack, inf_pkt, inf_pkt->seq);// move dgram to wait_ack queue
}
size_t ack_q_size = queue_entry_count(etcp->ack_q);
if (!inf_pkt && ack_q_size == 0) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: no data/ack to send");
return NULL;
}
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: got INFLIGHT_PACKET %p (seq=%u, len=%u)",
inf_pkt, inf_pkt->seq, inf_pkt->pkt_len);
inf_pkt->last_timestamp=get_current_time_units();
inf_pkt->send_count++;
inf_pkt->state=INFLIGHT_STATE_WAIT_ACK;
queue_data_put(etcp->input_send_q, inf_pkt, inf_pkt->seq);// move dgram to wait_ack queue
struct ETCP_DGRAM* dgram = memory_pool_alloc(etcp->instance->pkt_pool);
if (!dgram) {
@ -340,6 +404,7 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
dgram->noencrypt_len=0;
dgram->timestamp=get_current_timestamp();
// формат ack: [01] [elements count] [4 байта last_delivered_id] и <[4 байта seq][2 байта recv_ts][2 байта txrx delay ts]> x count
dgram->data[0]=1;// ack
int ptr=2;
@ -351,32 +416,40 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
// тут (потом) добавим опциональные заголовки
struct ACK_PACKET* ack_pkt;
while (ack_pkt = queue_data_get(etcp->ack_q)) {
// seq 4 байта
dgram->data[ptr++]=ack_pkt->seq;
dgram->data[ptr++]=ack_pkt->seq>>8;
dgram->data[ptr++]=ack_pkt->seq>>16;
dgram->data[ptr++]=ack_pkt->seq>>24;
// ts приема 2 байта
dgram->data[ptr++]=ack_pkt->recv_timestamp;
dgram->data[ptr++]=ack_pkt->recv_timestamp>>8;
// время задержки 2 байта между recv и ack
uint16_t dly=get_current_timestamp()-ack_pkt->recv_timestamp;
dgram->data[ptr++]=dly;
dgram->data[ptr++]=dly>>8;
// seq 4 байта
dgram->data[ptr++]=ack_pkt->seq;
dgram->data[ptr++]=ack_pkt->seq>>8;
dgram->data[ptr++]=ack_pkt->seq>>16;
dgram->data[ptr++]=ack_pkt->seq>>24;
// ts приема 2 байта
dgram->data[ptr++]=ack_pkt->recv_timestamp;
dgram->data[ptr++]=ack_pkt->recv_timestamp>>8;
// время задержки 2 байта между recv и ack
uint16_t dly=get_current_timestamp()-ack_pkt->recv_timestamp;
dgram->data[ptr++]=dly;
dgram->data[ptr++]=dly>>8;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: add ACK N%d dTS=%d", ack_pkt->seq, dly);
if (inf_pkt->pkt_len+ptr>=etcp->mtu-10) break;// pkt len (надо просчитать точнее включая все заголовки)
if (inf_pkt && inf_pkt->ll.size+ptr>=etcp->mtu-10) break;// pkt len (надо просчитать точнее включая все заголовки)
if (ptr>500) break;
}
dgram->data[1]=ptr/8;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: building packet data (seq=%u, len=%u)", inf_pkt->seq, inf_pkt->pkt_len);
dgram->data[ptr++]=0;// payload
memcpy(&dgram->data[ptr], inf_pkt->pkt_data, inf_pkt->pkt_len); ptr+=inf_pkt->pkt_len;
if (inf_pkt) {
// фрейм data (0) обязательно в конец
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: packet with payload (seq=%u, len=%u), ack_size=%d", inf_pkt->seq, inf_pkt->ll.size, dgram->data[1]);
dgram->data[ptr++]=0;// payload
memcpy(&dgram->data[ptr], inf_pkt->pkt_data, inf_pkt->ll.size); ptr+=inf_pkt->ll.size;
}
else {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: built packet with %d bytes total", dgram->data_len);
}
dgram->data_len=ptr;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: built packet with %d bytes total", dgram->data_len);
return dgram;
}
@ -397,7 +470,13 @@ static void etcp_conn_process_send_queue(struct ETCP_CONN* etcp) {
}
}
static void ack_response_timer_cb(void* arg) {// проверяем неотправленные ack response и отправляем если надо.
struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg;
etcp_conn_process_send_queue(etcp);// проталкиваем (она же должна отправлять только ack если больше ничего нет)
// если ack все еще заняты - обновляем таймаут
if (etcp->ack_q->count) etcp->ack_resp_timer = uasync_set_timeout(etcp->instance->ua, ACK_DELAY_TB, etcp, ack_response_timer_cb);
else etcp->ack_resp_timer=NULL;
}
// ====================================================================== Прием данных
@ -421,7 +500,7 @@ void etcp_output_try_assembly(struct ETCP_CONN* etcp) {
}
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_output_try_assembly: assembling packet id=%u (len=%u)",
rx_pkt->seq, rx_pkt->pkt_len);
rx_pkt->seq, rx_pkt->ll.size);
// Simply move ETCP_FRAGMENT from recv_q to output_queue - no data copying needed
// Remove from recv_q first
@ -429,7 +508,7 @@ void etcp_output_try_assembly(struct ETCP_CONN* etcp) {
// Add to output_queue using the same ETCP_FRAGMENT structure
if (queue_data_put(etcp->output_queue, rx_pkt, next_expected_id) == 0) {
delivered_bytes += rx_pkt->pkt_len;
delivered_bytes += rx_pkt->ll.size;
delivered_count++;
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_output_try_assembly: moved packet id=%u to output_queue",
next_expected_id);
@ -501,8 +580,8 @@ void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t d
}
// Update connection statistics
etcp->unacked_bytes -= acked_pkt->pkt_len;
etcp->bytes_sent_total += acked_pkt->pkt_len;
etcp->unacked_bytes -= acked_pkt->ll.size;
etcp->bytes_sent_total += acked_pkt->ll.size;
etcp->ack_packets_count++;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_ack_recv: removed packet seq=%u from wait_ack, unacked_bytes now %u",
@ -562,7 +641,10 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
p->pkt_timestamp=pkt->timestamp;
p->recv_timestamp=get_current_timestamp();
queue_data_put(etcp->ack_q, p, p->seq);
if (etcp->ack_resp_timer == NULL) {
etcp->ack_resp_timer = uasync_set_timeout(etcp->instance->ua, ACK_DELAY_TB, etcp, ack_response_timer_cb);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_conn_input: set ack_timer for delayed ACK send");
}
if ((int32_t)(etcp->last_delivered_id-seq)<0) if (queue_find_data_by_id(etcp->recv_q, seq)==NULL) {// проверяем есть ли пакет с этим seq
uint32_t pkt_len=len-5;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_conn_input: adding packet seq=%u to recv_q (last_delivered_id=%u)", seq, etcp->last_delivered_id);
@ -572,7 +654,7 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
rx_pkt->seq=seq;
rx_pkt->timestamp=pkt->timestamp;
rx_pkt->pkt_data=payload_data;
rx_pkt->pkt_len=pkt_len;
rx_pkt->ll.size=pkt_len;
// Copy the actual payload data
memcpy(payload_data, data + 5, pkt_len);
queue_data_put(etcp->recv_q, rx_pkt, seq);

6
src/etcp.h

@ -37,14 +37,12 @@ uint64_t get_current_time_units(void);
struct INFLIGHT_PACKET {
struct ll_entry ll;
struct ETCP_LINK* last_link; // Last sent link
void* pkt; // Packet (data для ll_entry)
uint64_t last_timestamp; // Last send timestamp
uint32_t seq; // packet seq (ID по документации)
uint8_t send_count; // Number of sends
uint8_t retrans_req_count; // Number of retrans requests
uint8_t state; // WAIT_ACK or WAIT_SEND
uint8_t* pkt_data;
uint16_t pkt_len;
};
// Список пакетов для сборки. собирается в ll_queue (используем быстрый поиск с хешем)
@ -53,7 +51,6 @@ struct ETCP_FRAGMENT {
uint32_t seq;
uint16_t timestamp;
uint8_t* pkt_data;
uint16_t pkt_len;
};
struct ACK_PACKET {
@ -104,6 +101,7 @@ struct ETCP_CONN {
uint32_t rx_ack_till;// из ack пакета - по какой пакет получено и собрано на уданенной стороне
// Metrics (RTT, jitter, etc.)
uint16_t retrans_delay;
uint16_t rtt_last;
uint16_t rtt_avg_10;
uint16_t rtt_avg_100;
@ -119,7 +117,7 @@ struct ETCP_CONN {
// Timers
void* retrans_timer; // Retrans check timer
void* ack_timer; // ACK send timer
void* ack_resp_timer; // ACK send timer
// Bandwidth measurement state
uint8_t burst_in_progress; // Burst transmission flag

102
src/etcp_connections.c

@ -15,105 +15,21 @@
#include "../lib/debug_config.h"
#include "etcp_loadbalancer.h"
#include <stdlib.h>
// Packet dump configuration
#define ETCP_PACKET_DUMP_COMPACT 1 // Use compact single-line format
#define ETCP_PACKET_DUMP_FULL 0 // Use full multi-line format (legacy)
#include <time.h>
// Use debug categories from debug_config.h
// Forward declaration
static void etcp_connections_read_callback(int fd, void* arg);
// Single-line packet dump for debug output
static void dump_packet_bytes(const char* prefix, const uint8_t* data, size_t len) {
// Build packet data as hex string for single-line output
char hex_buf[513]; // 256 bytes * 2 chars + 1 for null terminator
size_t hex_len = 0;
size_t show_len = (len > 128) ? 128 : len; // Show max 128 bytes
for (size_t i = 0; i < show_len && hex_len < 512 - 3; i++) {
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, "%02x", data[i]);
if (i < show_len - 1 && (i + 1) % 32 == 0) { // Add space every 32 bytes
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, " ");
}
}
if (len > 128) {
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, "...");
}
// Single-line debug output with packet info
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[ETCP] %s: len=%zu hex=%s", prefix, len, hex_buf);
// Additional debug info for first few bytes
if (len >= 2) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[ETCP] %s: first_bytes=%02x%02x last_bytes=%02x%02x",
prefix, data[0], data[1], data[len-2], data[len-1]);
}
}
// Compact packet info for high-frequency logging
static void log_packet_compact(const char* prefix, const uint8_t* data, size_t len, struct ETCP_LINK* link) {
if (!data || len == 0) return;
// Extract packet type from first byte
uint8_t pkt_type = data[0];
uint16_t timestamp = 0;
if (len >= 3) {
// Extract timestamp from bytes 1-2 (big endian)
timestamp = (data[1] << 8) | data[2];
}
// Single line debug with key packet info
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[ETCP] %s: link=%p type=0x%02x ts=%u len=%zu",
prefix, link, pkt_type, timestamp, len);
// Trace level for detailed hex dump
if (len <= 64) { // Only for small packets to avoid spam
char hex_buf[133]; // 64 bytes * 2 + 1
size_t hex_len = 0;
for (size_t i = 0; i < len && hex_len < sizeof(hex_buf) - 3; i++) {
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, "%02x", data[i]);
}
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[ETCP] %s: data=%s", prefix, hex_buf);
}
}
struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance);
static void etcp_link_remove_from_connections(struct ETCP_SOCKET* conn, struct ETCP_LINK* link);
// Unified packet dump function - uses configured format
static void packet_dump(const char* prefix, const uint8_t* data, size_t len, struct ETCP_LINK* link) {
if (!data || len == 0) return;
#if ETCP_PACKET_DUMP_COMPACT
// Compact single-line format
log_packet_compact(prefix, data, len, link);
#else
// Full multi-line format (legacy)
dump_packet_bytes(prefix, data, len);
#endif
}
// Legacy multi-line dump for compatibility (kept but not used)
static void dump_packet_bytes_multiline(const char* prefix, const uint8_t* data, size_t len) {
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[ETCP DUMP] %s: len=%zu; dump: ", prefix, len);
size_t show = len < 160 ? len : 160;
for (size_t i = 0; i < show; i++) {
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "%02x ", data[i]);
}
if (len > 160) DEBUG_INFO(DEBUG_CATEGORY_ETCP, "...");
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "\n");
log_dump(prefix, data, len);
}
// Forward declarations for missing functions
struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance);
// CONNECTION MANAGEMENT (!!!это всё должно быть static!!!)
static void etcp_link_remove_from_connections(struct ETCP_SOCKET* conn, struct ETCP_LINK* link);
// Отправка кодограмм протокола (!!!это всё должно быть static!!!)
static void etcp_link_send_init(struct ETCP_LINK* link);
static int etcp_link_send_reset(struct ETCP_LINK* link);
@ -498,7 +414,7 @@ int etcp_encrypt_send(struct ETCP_DGRAM* dgram) {
dgram->timestamp=get_current_timestamp();
// DUMP: Show packet before encryption
packet_dump("ECTP_ENCRYPT_SEND", dgram->data, dgram->data_len, dgram->link);
log_dump("ECTP_ENCRYPT_SEND", dgram->data, dgram->data_len);
sc_encrypt(sc, (uint8_t*)&dgram->timestamp/*не править это, тут верно!*/, sizeof(uint16_t) + len, enc_buf, &enc_buf_len);
if (enc_buf_len == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "etcp_encrypt_send: encryption failed for node %llu", (unsigned long long)dgram->link->etcp->instance->node_id);
@ -573,7 +489,7 @@ static void etcp_connections_read_callback(int fd, void* arg) {
// printf("[ETCP] Received packet: %zd bytes from address\n", recv_len);
// DUMP: Show received packet content
packet_dump("RECV in:", data, recv_len, NULL); // link unknown at this point
log_dump("RECV in:", data, recv_len); // link unknown at this point
struct ETCP_DGRAM* pkt = memory_pool_alloc(e_sock->instance->pkt_pool);
if (!pkt) return;
@ -628,6 +544,9 @@ static void etcp_connections_read_callback(int fd, void* arg) {
if (!conn) { errorcode=55; DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "etcp_connections_read_callback: failed to create connection"); goto ec_fr; }// облом
memcpy(&conn->crypto_ctx, &sc, sizeof(sc));// добавляем ключ
conn->peer_node_id=peer_id;
char buf[128];
addr_to_string(&addr, buf, sizeof(buf));
DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "New connection from %s peer_id=%ld etcp=%p", buf, peer_id, conn);
}
else {// check keys если существующее подключение
if (memcmp(conn->crypto_ctx.peer_public_key, sc.peer_public_key, SC_PUBKEY_SIZE)) { errorcode=5; DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "etcp_connections_read_callback: peer key mismatch for node %llu", (unsigned long long)peer_id); goto ec_fr; }// коллизия - peer id совпал а ключи разные.
@ -661,7 +580,10 @@ static void etcp_connections_read_callback(int fd, void* arg) {
}
if (sc_decrypt(&link->etcp->crypto_ctx, data, recv_len, (uint8_t*)&pkt->timestamp, &pkt_len)) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "etcp_connections_read_callback: failed to decrypt packet from node %llu", (unsigned long long)link->etcp->instance->node_id);
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "etcp_connections_read_callback: failed to decrypt packet from node %llu len=%d", (unsigned long long)link->etcp->instance->node_id, recv_len);
log_dump("my_privkey",&link->etcp->crypto_ctx.pk->private_key, SC_PRIVKEY_SIZE);
log_dump("my_pubkey",&link->etcp->crypto_ctx.pk->public_key, SC_PUBKEY_SIZE);
log_dump("peer_pubkey",&link->etcp->crypto_ctx.peer_public_key, SC_PUBKEY_SIZE);
errorcode=6;
goto ec_fr;
}

31
src/etcp_loadbalancer.c

@ -40,28 +40,24 @@ struct ETCP_LINK* etcp_loadbalancer_select_link(struct ETCP_CONN* etcp) {
while (link) {
link_index++;
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: link %d (%p) - initialized=%u, state=%u, load_tb=%llu, sub=%llu",
link_index, link, link->initialized, link->shaper_timer==NULL?1:0,
(unsigned long long)link->shaper_load_time_tb, (unsigned long long)link->shaper_sub_nanotime);
// DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: link %d (%p) - initialized=%u, state=%u, load_tb=%llu, sub=%llu",
// link_index, link, link->initialized, link->shaper_timer==NULL?1:0,
// (unsigned long long)link->shaper_load_time_tb, (unsigned long long)link->shaper_sub_nanotime);
if (!link->initialized) {
if (!link->initialized || link->shaper_timer) {// если установлен таймер - значит надо ждать. не рассматриваем этот линк
link = link->next;
continue;
}
if (link->shaper_load_time_tb < now_tb-ALLOWED_DELTA) link->shaper_load_time_tb = now_tb-ALLOWED_DELTA;
// Check if ready (load < now + burst allowance)
uint64_t effective_load_ns = link->shaper_load_time_tb * TIMEBASE_NS + (link->shaper_sub_nanotime / 10); // To ns
uint64_t now_ns = now_tb * TIMEBASE_NS;
int64_t delta_t=(int64_t)(now_ns - effective_load_ns);// uint64 может переполняться. чтобы обеспечить правильную цикличность.
if (delta_t > SHAPER_BURST_DELAY_TB) {
// Can send (within burst window)
if (link->shaper_load_time_tb < min_load_tb ||
(link->shaper_load_time_tb == min_load_tb && link->shaper_sub_nanotime < best->shaper_sub_nanotime)) {
min_load_tb = link->shaper_load_time_tb;
best = link;
}
if (link->shaper_load_time_tb < min_load_tb || (link->shaper_load_time_tb == min_load_tb && link->shaper_sub_nanotime < best->shaper_sub_nanotime)) {
min_load_tb = link->shaper_load_time_tb;
best = link;
}
link = link->next;
}
@ -86,7 +82,8 @@ void etcp_loadbalancer_send(struct ETCP_DGRAM* dgram) {
struct ETCP_CONN* etcp = dgram->link ? dgram->link->etcp : NULL;
if (!etcp) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_send: no ETCP_CONN associated with dgram");
free(dgram);
memory_pool_free(etcp->instance->pkt_pool, dgram);
// free(dgram);
return;
}
@ -95,7 +92,8 @@ void etcp_loadbalancer_send(struct ETCP_DGRAM* dgram) {
dgram->link = etcp_loadbalancer_select_link(etcp);
if (!dgram->link) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_send: no link available, dropping dgram");
free(dgram); // Assume free; adjust if pooled
memory_pool_free(etcp->instance->pkt_pool, dgram);
// free(dgram); // Assume free; adjust if pooled
return;
}
}
@ -107,7 +105,7 @@ void etcp_loadbalancer_send(struct ETCP_DGRAM* dgram) {
// Encrypt and send (from etcp_connections.c)
int send_result = etcp_encrypt_send(dgram);
if (send_result != 0) {
if (send_result < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_send: encrypt/send failed (%d)", send_result);
// Don't return here, let the function continue to free dgram at the end
}
@ -144,7 +142,8 @@ void etcp_loadbalancer_send(struct ETCP_DGRAM* dgram) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_send: updated load_tb=%llu, sub=%llu, state=%u",
(unsigned long long)link->shaper_load_time_tb, (unsigned long long)link->shaper_sub_nanotime, link->shaper_timer==NULL?1:0);
free(dgram); // Free the dgram in all cases - we own it
memory_pool_free(etcp->instance->pkt_pool, dgram);
// free(dgram); // Free the dgram in all cases - we own it
}
// New: Notify when link is ready (called from timer or external)

15
src/pkt_normalizer.c

@ -4,8 +4,8 @@
#include <string.h>
#include <stdint.h>
#include <stdio.h>
static void packer_handler(struct ll_queue* q, void* data, void* arg);
static void unpacker_handler(struct ll_queue* q, void* data, void* arg);
static void packer_handler(struct ll_queue* q, void* arg);
static void unpacker_handler(struct ll_queue* q, void* arg);
static void send_buf(struct pn_struct* pn);
static int get_header(uint8_t* header, size_t L);
/* Calculate fragment size from mtu */
@ -117,7 +117,7 @@ static void send_buf(struct pn_struct* pn) {
queue_data_put(pn->output, out, 0); // Новый API, ID=0 для внутренних пакетов
pn->u.packer.len = 0;
}
static void packer_handler(struct ll_queue* q, void* data, void* arg) {
static void packer_handler(struct ll_queue* q, void* arg) {
struct pn_struct* pn = arg;
size_t max = (size_t)1400;
@ -228,7 +228,7 @@ static void packer_handler(struct ll_queue* q, void* data, void* arg) {
*(uint16_t*)fd = (uint16_t)payload_len;
fd += 2;
*fd++ = 0xFE;
memcpy(fd, data + pos, chunk);
memcpy(fd, pkt_data + pos, chunk);
queue_data_put(pn->output, fout, 0);
pos += chunk;
remaining -= chunk;
@ -252,7 +252,7 @@ static void packer_handler(struct ll_queue* q, void* data, void* arg) {
*fd++ = 0xFE;
}
}
memcpy(fd, data + pos, chunk);
memcpy(fd, pkt_data + pos, chunk);
queue_data_put(pn->output, fout, 0);
pos += chunk;
remaining -= chunk;
@ -265,7 +265,7 @@ static void packer_handler(struct ll_queue* q, void* data, void* arg) {
// Add to buffer
uint8_t* p = pn->u.packer.buf + pn->u.packer.len;
memcpy(p, header, (size_t)hsize);
memcpy(p + hsize, data, L);
memcpy(p + hsize, pkt_data, L);
pn->u.packer.len += needed;
}
queue_data_free(pkt_data);
@ -379,8 +379,7 @@ void pkt_normalizer_flush(struct pn_struct* pn) {
send_buf(pn);
}
}
static void unpacker_handler(struct ll_queue* q, void* data, void* arg) {
(void)data;
static void unpacker_handler(struct ll_queue* q, void* arg) {
struct pn_struct* pn = arg;
while (queue_entry_count(q) > 0) {
uint8_t* entry = queue_data_get(q);

45
tests/1

@ -55,4 +55,47 @@ c5 94 f3 3c 91 f3 a2 22 27 95 c2 c1 10 c5 27 bf 21 4a d1 00 91 97 ce 14 55 6c b1
[ETCP DUMP] RECV decrypted:: len=11; dump: 03 11 11 11 11 11 11 11 11 00 00
[ETCP] code=3
INFO: Received INIT_RESPONSE from server_node_id=1229782938247303441, mtu=0
[ETCP] Link initialized successfully! Server node_id=1229782938247303441, m
[ETCP] Link initialized successfully! Server node_id=1229782938247303441, mmake[1]: *** Нет правила для сборки цели «test_ll_queue_new.c», требуемой для «test_ll_queue-test_ll_queue_new.o». Останов.
make: *** [Makefile:1494: check-am] Ошибка 2
test_ll_queue.c:20: warning: "DEBUG_CATEGORY_LL_QUEUE" redefined
20 | #define DEBUG_CATEGORY_LL_QUEUE 1
|
In file included from test_ll_queue.c:16:
../lib/debug_config.h:32: note: this is the location of the previous definition
32 | #define DEBUG_CATEGORY_LL_QUEUE ((debug_category_t)1 << 1) // ll_queue module
|
test_ll_queue.c: In function ‘test_memory_pool_integration’:
test_ll_queue.c:670:21: error: ‘entry_data3’ undeclared (first use in this function)
670 | queue_data_free(entry_data3);
| ^~~~~~~~~~~
test_ll_queue.c:670:21: note: each undeclared identifier is reported only once for each function it appears in
test_ll_queue.c: In function ‘test_edge_cases’:
test_ll_queue.c:719:21: error: ‘entry_retrieved’ undeclared (first use in this function)
719 | queue_data_free(entry_retrieved);
| ^~~~~~~~~~~~~~~
test_ll_queue.c: In function ‘test_memory_pool_vs_malloc_performance’:
test_ll_queue.c:1005:31: error: ‘entry_data’ undeclared (first use in this function)
1005 | queue_data_put(q, entry_data, data->id);
| ^~~~~~~~~~
make: *** [Makefile:1086: test_ll_queue-test_ll_queue.o] Ошибка 1
test_ll_queue.c:20: warning: "DEBUG_CATEGORY_LL_QUEUE" redefined
20 | #define DEBUG_CATEGORY_LL_QUEUE 1
|
In file included from test_ll_queue.c:16:
../lib/debug_config.h:32: note: this is the location of the previous definition
32 | #define DEBUG_CATEGORY_LL_QUEUE ((debug_category_t)1 << 1) // ll_queue module
|
test_ll_queue.c:20: warning: "DEBUG_CATEGORY_LL_QUEUE" redefined
20 | #define DEBUG_CATEGORY_LL_QUEUE 1
|
In file included from test_ll_queue.c:16:
../lib/debug_config.h:32: note: this is the location of the previous definition
32 | #define DEBUG_CATEGORY_LL_QUEUE ((debug_category_t)1 << 1) // ll_queue module
|
test_ll_queue.c:20: warning: "DEBUG_CATEGORY_LL_QUEUE" redefined
20 | #define DEBUG_CATEGORY_LL_QUEUE 1
|
In file included from test_ll_queue.c:16:
../lib/debug_config.h:32: note: this is the location of the previous definition
32 | #define DEBUG_CATEGORY_LL_QUEUE ((debug_category_t)1 << 1) // ll_queue module
|

BIN
tests/test_config_debug

Binary file not shown.

BIN
tests/test_crypto

Binary file not shown.

BIN
tests/test_debug_categories

Binary file not shown.

BIN
tests/test_ecc_encrypt

Binary file not shown.

BIN
tests/test_etcp_crypto

Binary file not shown.

BIN
tests/test_etcp_minimal

Binary file not shown.

BIN
tests/test_etcp_simple_traffic

Binary file not shown.

491
tests/test_etcp_simple_traffic.c.backup

@ -1,491 +0,0 @@
// test_etcp_simple_traffic.c - Simplified ETCP traffic flow debugging without sockets
// This test focuses purely on ETCP protocol traffic analysis using direct packet injection
#include "../src/etcp.h"
#include "../src/etcp_connections.h"
#include "../src/etcp_loadbalancer.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
#include "../lib/memory_pool.h"
#include "../lib/ll_queue.h"
#include "../src/secure_channel.h"
#include "../src/crc32.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <time.h>
#include <errno.h>
// Simplified traffic analysis - focus on ETCP protocol packets only
typedef struct {
uint64_t timestamp;
uint16_t packet_id;
uint8_t packet_type;
uint16_t data_len;
uint8_t data[1500]; // Copy of packet data for analysis
} packet_capture_t;
typedef struct {
packet_capture_t captures[1000];
size_t capture_count;
pthread_mutex_t lock;
} traffic_analyzer_t;
// Test instance - simplified version without actual sockets
typedef struct {
struct UTUN_INSTANCE* instance;
struct ETCP_CONN* etcp_conn;
traffic_analyzer_t* analyzer;
uint64_t node_id;
int is_server;
int running;
} test_instance_t;
// Global test state
static test_instance_t* server_instance = NULL;
static test_instance_t* client_instance = NULL;
// Enhanced packet analysis function
static void analyze_etcp_packet(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len, const char* direction) {
if (!etcp || !data || len == 0) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "analyze_etcp_packet: invalid parameters (etcp=%p, data=%p, len=%u)",
etcp, data, len);
return;
}
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "PACKET %s: etcp=%p, len=%u, first_byte=0x%02x",
direction, etcp, len, data[0]);
// Parse ETCP sections
uint16_t pos = 0;
int section_count = 0;
while (pos < len) {
if (pos + 3 > len) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "Invalid section header at pos %u (need 3 bytes, have %u)",
pos, len - pos);
break;
}
uint8_t section_type = data[pos];
uint16_t section_len = (data[pos+1] << 8) | data[pos+2];
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "Section %d: type=0x%02x, len=%u at pos=%u",
section_count, section_type, section_len, pos);
if (pos + 3 + section_len > len || section_len > 1500) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Section length %u exceeds packet boundary (pos=%u, total=%u) or max size",
section_len, pos, len);
break;
}
// Analyze specific section types
uint8_t* section_data = data + pos + 3;
switch (section_type) {
case ETCP_SECTION_PAYLOAD: {
if (section_len >= 2) {
uint16_t packet_id = (section_data[0] << 8) | section_data[1];
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, " PAYLOAD: packet_id=%u, payload_len=%u",
packet_id, section_len - 2);
}
break;
}
case ETCP_SECTION_ACK: {
if (section_len >= 1) {
uint8_t ack_count = section_data[0];
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, " ACK: count=%u", ack_count);
for (int i = 0; i < ack_count && i*4+5 <= section_len; i++) {
uint16_t ack_id = (section_data[1+i*4] << 8) | section_data[2+i*4];
uint16_t ack_ts = (section_data[3+i*4] << 8) | section_data[4+i*4];
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, " ACK[%d]: id=%u, ts=%u", i, ack_id, ack_ts);
}
}
break;
}
case ETCP_SECTION_TIMESTAMP: {
if (section_len == 2) {
uint16_t ts = (section_data[0] << 8) | section_data[1];
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, " TIMESTAMP: ts=%u", ts);
}
break;
}
case ETCP_INIT_REQUEST: {
if (section_len >= 76) { // node_id(8) + mtu(2) + keepalive(2) + pubkey(64)
uint64_t node_id = 0;
for (int i = 0; i < 8; i++) {
node_id = (node_id << 8) | section_data[i];
}
uint16_t mtu = (section_data[8] << 8) | section_data[9];
uint16_t keepalive = (section_data[10] << 8) | section_data[11];
DEBUG_INFO(DEBUG_CATEGORY_ETCP, " INIT_REQUEST: node_id=%llu, mtu=%u, keepalive=%u",
(unsigned long long)node_id, mtu, keepalive);
}
break;
}
case ETCP_INIT_RESPONSE: {
if (section_len >= 10) { // node_id(8) + mtu(2)
uint64_t node_id = 0;
for (int i = 0; i < 8; i++) {
node_id = (node_id << 8) | section_data[i];
}
uint16_t mtu = (section_data[8] << 8) | section_data[9];
DEBUG_INFO(DEBUG_CATEGORY_ETCP, " INIT_RESPONSE: node_id=%llu, mtu=%u",
(unsigned long long)node_id, mtu);
}
break;
}
default: {
if (section_type >= ETCP_SECTION_RETRANS && section_type <= ETCP_SECTION_RETRANS + 0x1F) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, " RETRANS: base_id=%u", section_type - ETCP_SECTION_RETRANS);
// Parse retransmission requests
for (int i = 0; i < section_len; i += 2) {
if (i+1 < section_len) {
uint16_t req_id = (section_data[i] << 8) | section_data[i+1];
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, " RETRANS_REQ: id=%u", req_id);
}
}
} else {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, " UNKNOWN: type=0x%02x, len=%u", section_type, section_len);
}
break;
}
}
pos += 3 + section_len;
section_count++;
}
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Packet analysis complete: %d sections processed", section_count);
}
// Simulate packet reception for analysis
static void simulate_packet_rx(test_instance_t* inst, uint8_t* data, uint16_t len) {
if (!inst || !inst->etcp_conn || !data) return;
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "SIMULATE RX: inst=%s, len=%u",
inst->is_server ? "server" : "client", len);
// Analyze the packet first
analyze_etcp_packet(inst->etcp_conn, data, len, "SIM_RX");
// Create a mock ETCP_DGRAM for processing
struct ETCP_DGRAM mock_dgram;
memset(&mock_dgram, 0, sizeof(mock_dgram));
mock_dgram.link = NULL; // Will be set by connection processing
mock_dgram.data_len = len > 1500 ? 1500 : len;
mock_dgram.timestamp = get_current_timestamp();
if (mock_dgram.data_len > 0) {
memcpy(mock_dgram.data, data, mock_dgram.data_len);
}
// Process through ETCP input
etcp_conn_input(&mock_dgram);
}
// Simulate packet transmission for analysis
static void simulate_packet_tx(test_instance_t* inst) {
if (!inst || !inst->etcp_conn) return;
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "SIMULATE TX: inst=%s",
inst->is_server ? "server" : "client");
struct ETCP_DGRAM* dgram = etcp_request_pkt(inst->etcp_conn);
if (dgram) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Generated packet: len=%u", dgram->data_len);
analyze_etcp_packet(inst->etcp_conn, dgram->data, dgram->data_len, "SIM_TX");
// In a real scenario, this would be sent over the network
// For now, we just analyze it and free it
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "Would send %u bytes to peer", dgram->data_len);
} else {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "No packets available to send");
}
}
// Create simplified test instance
static test_instance_t* create_simple_instance(uint64_t node_id, int is_server) {
test_instance_t* inst = calloc(1, sizeof(test_instance_t));
if (!inst) return NULL;
inst->node_id = node_id;
inst->is_server = is_server;
inst->running = 1;
// Create traffic analyzer
inst->analyzer = calloc(1, sizeof(traffic_analyzer_t));
if (inst->analyzer) {
pthread_mutex_init(&inst->analyzer->lock, NULL);
}
// Create UTUN instance directly without TUN device
inst->instance = calloc(1, sizeof(struct UTUN_INSTANCE));
if (!inst->instance) {
free(inst->analyzer);
free(inst);
return NULL;
}
// Initialize the instance manually
inst->instance->node_id = node_id;
inst->instance->running = 1;
inst->instance->log_fp = stderr;
// Initialize crypto keys (hardcoded for test)
const char* priv_key_hex = "67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb";
const char* pub_key_hex = "1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9";
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Parsing crypto keys: priv_len=%zu, pub_len=%zu",
strlen(priv_key_hex), strlen(pub_key_hex));
// Parse keys
for (int i = 0; i < 32; i++) {
if (sscanf(&priv_key_hex[i*2], "%2hhx", &inst->instance->my_keys.private_key[i]) != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to parse private key byte %d", i);
}
}
for (int i = 0; i < 64; i++) {
if (sscanf(&pub_key_hex[i*2], "%2hhx", &inst->instance->my_keys.public_key[i]) != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to parse public key byte %d", i);
}
}
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Keys parsed successfully");
// Create uasync context
inst->instance->ua = uasync_create();
if (!inst->instance->ua) {
free(inst->instance);
free(inst->analyzer);
free(inst);
return NULL;
}
uasync_init_instance(inst->instance->ua);
// Create packet pool
inst->instance->pkt_pool = memory_pool_init(1600); // 1600 byte packets
if (!inst->instance->pkt_pool) {
uasync_destroy(inst->instance->ua);
free(inst->instance);
free(inst->analyzer);
free(inst);
return NULL;
}
// Create ETCP connection
inst->etcp_conn = etcp_connection_create(inst->instance);
if (!inst->etcp_conn) {
memory_pool_destroy(inst->instance->pkt_pool);
uasync_destroy(inst->instance->ua);
free(inst->instance);
free(inst->analyzer);
free(inst);
return NULL;
}
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Created %s instance (node_id=%llu)",
is_server ? "server" : "client", (unsigned long long)node_id);
return inst;
}
// Cleanup simplified test instance
static void destroy_simple_instance(test_instance_t* inst) {
if (!inst) return;
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Destroying %s instance", inst->is_server ? "server" : "client");
if (inst->etcp_conn) {
etcp_connection_close(inst->etcp_conn);
}
if (inst->instance) {
if (inst->instance->pkt_pool) {
memory_pool_destroy(inst->instance->pkt_pool);
}
if (inst->instance->ua) {
uasync_destroy(inst->instance->ua);
}
free(inst->instance);
}
if (inst->analyzer) {
pthread_mutex_destroy(&inst->analyzer->lock);
free(inst->analyzer);
}
free(inst);
}
// Generate test packets manually
static void generate_test_packets(test_instance_t* inst, int count) {
if (!inst || !inst->etcp_conn) return;
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Generating %d test packets for %s instance",
count, inst->is_server ? "server" : "client");
for (int i = 0; i < count; i++) {
struct ll_entry* entry = queue_entry_new(100); // 100 byte packets
if (entry) {
// Fill with test data
uint8_t* data = (uint8_t*)ll_entry_data(entry);
for (int j = 0; j < 100; j++) {
data[j] = (uint8_t)((i+1) * 10 + j); // Pattern data
}
queue_entry_put(inst->etcp_conn->input_queue, entry);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Queued test packet %d for %s",
i+1, inst->is_server ? "server" : "client");
}
}
}
// Main test function
int main(int argc, char* argv[]) {
printf("=== ETCP Traffic Flow Debugging Test (Simplified) ===\n");
// Initialize debug system
debug_config_init();
debug_set_level(DEBUG_LEVEL_DEBUG);
debug_enable_category(DEBUG_CATEGORY_ETCP);
debug_enable_timestamp(1);
debug_enable_function_name(1);
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Starting simplified ETCP traffic flow debugging test");
// Create server instance
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating server instance...");
server_instance = create_simple_instance(0x1111111111111111ULL, 1);
if (!server_instance) {
fprintf(stderr, "Failed to create server instance\n");
return 1;
}
// Create client instance
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating client instance...");
client_instance = create_simple_instance(0x2222222222222222ULL, 0);
if (!client_instance) {
fprintf(stderr, "Failed to create client instance\n");
destroy_simple_instance(server_instance);
return 1;
}
// Phase 1: Test basic packet generation and analysis
printf("\n=== Phase 1: Basic Packet Generation ===\n");
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Generating test packets...");
generate_test_packets(server_instance, 5);
generate_test_packets(client_instance, 5);
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Simulating packet transmission...");
// Let server generate some packets
for (int i = 0; i < 3; i++) {
simulate_packet_tx(server_instance);
usleep(10000); // 1ms delay
}
// Let client generate some packets
for (int i = 0; i < 3; i++) {
simulate_packet_tx(client_instance);
usleep(10000); // 1ms delay
}
// Phase 2: Test connection establishment simulation
printf("\n=== Phase 2: Connection Establishment Simulation ===\n");
// Simulate INIT_REQUEST from client - smaller version for testing
uint8_t init_request[] = {
ETCP_INIT_REQUEST, // Section type
0x00, 0x14, // Section length (20 bytes) - smaller for test
// Node ID (8 bytes)
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
// MTU (2 bytes)
0x05, 0xDC, // 1500
// Keepalive (2 bytes)
0x00, 0x64, // 100
// Fake public key (8 bytes) - just for testing
0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22
};
// Total: 3 + 20 = 23 bytes
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Simulating INIT_REQUEST from client...");
simulate_packet_rx(server_instance, init_request, sizeof(init_request));
// Simulate INIT_RESPONSE from server
uint8_t init_response[] = {
ETCP_INIT_RESPONSE, // Section type
0x00, 0x0A, // Section length (10 bytes)
// Node ID (8 bytes)
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
// MTU (2 bytes)
0x05, 0xDC // 1500
};
// Total: 3 + 10 = 13 bytes
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Simulating INIT_RESPONSE from server...");
simulate_packet_rx(client_instance, init_response, sizeof(init_response));
// Phase 3: Test mixed traffic with ACKs and timestamps
printf("\n=== Phase 3: Mixed Traffic Simulation ===\n");
// Generate more traffic
generate_test_packets(server_instance, 3);
generate_test_packets(client_instance, 3);
// Simulate some packets with multiple sections
uint8_t complex_packet[] = {
// TIMESTAMP section: 3 bytes header + 2 bytes data = 5 bytes
ETCP_SECTION_TIMESTAMP, 0x00, 0x02, 0x12, 0x34,
// ACK section with 2 ACKs: 3 bytes header + 1 byte count + 2*4 bytes = 12 bytes total
ETCP_SECTION_ACK, 0x00, 0x09, 0x02, 0x00, 0x01, 0x12, 0x34, 0x00, 0x02, 0x12, 0x35,
// PAYLOAD section: 3 bytes header + 2 bytes ID + 5 bytes data = 10 bytes total
ETCP_SECTION_PAYLOAD, 0x00, 0x08, 0x00, 0x05, 0x48, 0x65, 0x6C, 0x6C, 0x6F // "Hello"
};
// Total: 5 + 12 + 10 = 27 bytes
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Simulating complex packet with multiple sections...");
simulate_packet_rx(server_instance, complex_packet, sizeof(complex_packet));
// Phase 4: Test retransmission requests
printf("\n=== Phase 4: Retransmission Testing ===\n");
uint8_t retrans_request[] = {
ETCP_SECTION_RETRANS + 0x02, // RETRANS section with base ID 2
0x00, 0x04, // Section length (4 bytes)
0x00, 0x03, // Request retransmission of packet ID 3
0x00, 0x04 // Request retransmission of packet ID 4
};
// Total: 3 + 4 = 7 bytes
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Simulating retransmission request...");
simulate_packet_rx(client_instance, retrans_request, sizeof(retrans_request));
// Final packet generation
printf("\n=== Final Phase: Additional Traffic ===\n");
// Generate final packets
for (int i = 0; i < 5; i++) {
simulate_packet_tx(server_instance);
simulate_packet_tx(client_instance);
usleep(5000); // 0.5ms delay
}
// Print summary
printf("\n=== Traffic Analysis Summary ===\n");
printf("Server instance: processed packets with detailed section analysis\n");
printf("Client instance: processed packets with detailed section analysis\n");
printf("Connection establishment: INIT handshake simulated\n");
printf("Mixed traffic: ACK, TIMESTAMP, PAYLOAD sections processed\n");
printf("Retransmission: RETRANS requests processed\n");
// Cleanup
destroy_simple_instance(server_instance);
destroy_simple_instance(client_instance);
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Test completed");
printf("\n=== Test completed successfully ===\n");
return 0;
}

491
tests/test_etcp_simple_traffic.c.orig

@ -1,491 +0,0 @@
// test_etcp_simple_traffic.c - Simplified ETCP traffic flow debugging without sockets
// This test focuses purely on ETCP protocol traffic analysis using direct packet injection
#include "../src/etcp.h"
#include "../src/etcp_connections.h"
#include "../src/etcp_loadbalancer.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
#include "../lib/memory_pool.h"
#include "../lib/ll_queue.h"
#include "../src/secure_channel.h"
#include "../src/crc32.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <time.h>
#include <errno.h>
// Simplified traffic analysis - focus on ETCP protocol packets only
typedef struct {
uint64_t timestamp;
uint16_t packet_id;
uint8_t packet_type;
uint16_t data_len;
uint8_t data[1500]; // Copy of packet data for analysis
} packet_capture_t;
typedef struct {
packet_capture_t captures[1000];
size_t capture_count;
pthread_mutex_t lock;
} traffic_analyzer_t;
// Test instance - simplified version without actual sockets
typedef struct {
struct UTUN_INSTANCE* instance;
struct ETCP_CONN* etcp_conn;
traffic_analyzer_t* analyzer;
uint64_t node_id;
int is_server;
int running;
} test_instance_t;
// Global test state
static test_instance_t* server_instance = NULL;
static test_instance_t* client_instance = NULL;
// Enhanced packet analysis function
static void analyze_etcp_packet(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len, const char* direction) {
if (!etcp || !data || len == 0) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "analyze_etcp_packet: invalid parameters (etcp=%p, data=%p, len=%u)",
etcp, data, len);
return;
}
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "PACKET %s: etcp=%p, len=%u, first_byte=0x%02x",
direction, etcp, len, data[0]);
// Parse ETCP sections
uint16_t pos = 0;
int section_count = 0;
while (pos < len) {
if (pos + 3 > len) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "Invalid section header at pos %u (need 3 bytes, have %u)",
pos, len - pos);
break;
}
uint8_t section_type = data[pos];
uint16_t section_len = (data[pos+1] << 8) | data[pos+2];
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "Section %d: type=0x%02x, len=%u at pos=%u",
section_count, section_type, section_len, pos);
if (pos + 3 + section_len > len || section_len > 1500) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Section length %u exceeds packet boundary (pos=%u, total=%u) or max size",
section_len, pos, len);
break;
}
// Analyze specific section types
uint8_t* section_data = data + pos + 3;
switch (section_type) {
case ETCP_SECTION_PAYLOAD: {
if (section_len >= 2) {
uint16_t packet_id = (section_data[0] << 8) | section_data[1];
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, " PAYLOAD: packet_id=%u, payload_len=%u",
packet_id, section_len - 2);
}
break;
}
case ETCP_SECTION_ACK: {
if (section_len >= 1) {
uint8_t ack_count = section_data[0];
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, " ACK: count=%u", ack_count);
for (int i = 0; i < ack_count && i*4+5 <= section_len; i++) {
uint16_t ack_id = (section_data[1+i*4] << 8) | section_data[2+i*4];
uint16_t ack_ts = (section_data[3+i*4] << 8) | section_data[4+i*4];
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, " ACK[%d]: id=%u, ts=%u", i, ack_id, ack_ts);
}
}
break;
}
case ETCP_SECTION_TIMESTAMP: {
if (section_len == 2) {
uint16_t ts = (section_data[0] << 8) | section_data[1];
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, " TIMESTAMP: ts=%u", ts);
}
break;
}
case ETCP_INIT_REQUEST: {
if (section_len >= 76) { // node_id(8) + mtu(2) + keepalive(2) + pubkey(64)
uint64_t node_id = 0;
for (int i = 0; i < 8; i++) {
node_id = (node_id << 8) | section_data[i];
}
uint16_t mtu = (section_data[8] << 8) | section_data[9];
uint16_t keepalive = (section_data[10] << 8) | section_data[11];
DEBUG_INFO(DEBUG_CATEGORY_ETCP, " INIT_REQUEST: node_id=%llu, mtu=%u, keepalive=%u",
(unsigned long long)node_id, mtu, keepalive);
}
break;
}
case ETCP_INIT_RESPONSE: {
if (section_len >= 10) { // node_id(8) + mtu(2)
uint64_t node_id = 0;
for (int i = 0; i < 8; i++) {
node_id = (node_id << 8) | section_data[i];
}
uint16_t mtu = (section_data[8] << 8) | section_data[9];
DEBUG_INFO(DEBUG_CATEGORY_ETCP, " INIT_RESPONSE: node_id=%llu, mtu=%u",
(unsigned long long)node_id, mtu);
}
break;
}
default: {
if (section_type >= ETCP_SECTION_RETRANS && section_type <= ETCP_SECTION_RETRANS + 0x1F) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, " RETRANS: base_id=%u", section_type - ETCP_SECTION_RETRANS);
// Parse retransmission requests
for (int i = 0; i < section_len; i += 2) {
if (i+1 < section_len) {
uint16_t req_id = (section_data[i] << 8) | section_data[i+1];
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, " RETRANS_REQ: id=%u", req_id);
}
}
} else {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, " UNKNOWN: type=0x%02x, len=%u", section_type, section_len);
}
break;
}
}
pos += 3 + section_len;
section_count++;
}
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Packet analysis complete: %d sections processed", section_count);
}
// Simulate packet reception for analysis
static void simulate_packet_rx(test_instance_t* inst, uint8_t* data, uint16_t len) {
if (!inst || !inst->etcp_conn || !data) return;
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "SIMULATE RX: inst=%s, len=%u",
inst->is_server ? "server" : "client", len);
// Analyze the packet first
analyze_etcp_packet(inst->etcp_conn, data, len, "SIM_RX");
// Create a mock ETCP_DGRAM for processing
struct ETCP_DGRAM mock_dgram;
memset(&mock_dgram, 0, sizeof(mock_dgram));
mock_dgram.link = NULL; // Will be set by connection processing
mock_dgram.data_len = len > 1500 ? 1500 : len;
mock_dgram.timestamp = get_current_timestamp();
if (mock_dgram.data_len > 0) {
memcpy(mock_dgram.data, data, mock_dgram.data_len);
}
// Process through ETCP input
etcp_conn_input(&mock_dgram);
}
// Simulate packet transmission for analysis
static void simulate_packet_tx(test_instance_t* inst) {
if (!inst || !inst->etcp_conn) return;
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "SIMULATE TX: inst=%s",
inst->is_server ? "server" : "client");
struct ETCP_DGRAM* dgram = etcp_request_pkt(inst->etcp_conn);
if (dgram) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Generated packet: len=%u", dgram->data_len);
analyze_etcp_packet(inst->etcp_conn, dgram->data, dgram->data_len, "SIM_TX");
// In a real scenario, this would be sent over the network
// For now, we just analyze it and free it
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "Would send %u bytes to peer", dgram->data_len);
} else {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "No packets available to send");
}
}
// Create simplified test instance
static test_instance_t* create_simple_instance(uint64_t node_id, int is_server) {
test_instance_t* inst = calloc(1, sizeof(test_instance_t));
if (!inst) return NULL;
inst->node_id = node_id;
inst->is_server = is_server;
inst->running = 1;
// Create traffic analyzer
inst->analyzer = calloc(1, sizeof(traffic_analyzer_t));
if (inst->analyzer) {
pthread_mutex_init(&inst->analyzer->lock, NULL);
}
// Create UTUN instance directly without TUN device
inst->instance = calloc(1, sizeof(struct UTUN_INSTANCE));
if (!inst->instance) {
free(inst->analyzer);
free(inst);
return NULL;
}
// Initialize the instance manually
inst->instance->node_id = node_id;
inst->instance->running = 1;
inst->instance->log_fp = stderr;
// Initialize crypto keys (hardcoded for test)
const char* priv_key_hex = "67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb";
const char* pub_key_hex = "1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9";
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Parsing crypto keys: priv_len=%zu, pub_len=%zu",
strlen(priv_key_hex), strlen(pub_key_hex));
// Parse keys
for (int i = 0; i < 32; i++) {
if (sscanf(&priv_key_hex[i*2], "%2hhx", &inst->instance->my_keys.private_key[i]) != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to parse private key byte %d", i);
}
}
for (int i = 0; i < 64; i++) {
if (sscanf(&pub_key_hex[i*2], "%2hhx", &inst->instance->my_keys.public_key[i]) != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to parse public key byte %d", i);
}
}
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Keys parsed successfully");
// Create uasync context
inst->instance->ua = uasync_create();
if (!inst->instance->ua) {
free(inst->instance);
free(inst->analyzer);
free(inst);
return NULL;
}
uasync_init_instance(inst->instance->ua);
// Create packet pool
inst->instance->pkt_pool = memory_pool_init(1600); // 1600 byte packets
if (!inst->instance->pkt_pool) {
uasync_destroy(inst->instance->ua);
free(inst->instance);
free(inst->analyzer);
free(inst);
return NULL;
}
// Create ETCP connection
inst->etcp_conn = etcp_connection_create(inst->instance);
if (!inst->etcp_conn) {
memory_pool_destroy(inst->instance->pkt_pool);
uasync_destroy(inst->instance->ua);
free(inst->instance);
free(inst->analyzer);
free(inst);
return NULL;
}
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Created %s instance (node_id=%llu)",
is_server ? "server" : "client", (unsigned long long)node_id);
return inst;
}
// Cleanup simplified test instance
static void destroy_simple_instance(test_instance_t* inst) {
if (!inst) return;
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Destroying %s instance", inst->is_server ? "server" : "client");
if (inst->etcp_conn) {
etcp_connection_close(inst->etcp_conn);
}
if (inst->instance) {
if (inst->instance->pkt_pool) {
memory_pool_destroy(inst->instance->pkt_pool);
}
if (inst->instance->ua) {
uasync_destroy(inst->instance->ua);
}
free(inst->instance);
}
if (inst->analyzer) {
pthread_mutex_destroy(&inst->analyzer->lock);
free(inst->analyzer);
}
free(inst);
}
// Generate test packets manually
static void generate_test_packets(test_instance_t* inst, int count) {
if (!inst || !inst->etcp_conn) return;
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Generating %d test packets for %s instance",
count, inst->is_server ? "server" : "client");
for (int i = 0; i < count; i++) {
struct ll_entry* entry = queue_entry_new(100); // 100 byte packets
if (entry) {
// Fill with test data
uint8_t* data = (uint8_t*)ll_entry_data(entry);
for (int j = 0; j < 100; j++) {
data[j] = (uint8_t)((i+1) * 10 + j); // Pattern data
}
queue_entry_put(inst->etcp_conn->input_queue, entry);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Queued test packet %d for %s",
i+1, inst->is_server ? "server" : "client");
}
}
}
// Main test function
int main(int argc, char* argv[]) {
printf("=== ETCP Traffic Flow Debugging Test (Simplified) ===\n");
// Initialize debug system
debug_config_init();
debug_set_level(DEBUG_LEVEL_DEBUG);
debug_enable_category(DEBUG_CATEGORY_ETCP);
debug_enable_timestamp(1);
debug_enable_function_name(1);
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Starting simplified ETCP traffic flow debugging test");
// Create server instance
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating server instance...");
server_instance = create_simple_instance(0x1111111111111111ULL, 1);
if (!server_instance) {
fprintf(stderr, "Failed to create server instance\n");
return 1;
}
// Create client instance
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating client instance...");
client_instance = create_simple_instance(0x2222222222222222ULL, 0);
if (!client_instance) {
fprintf(stderr, "Failed to create client instance\n");
destroy_simple_instance(server_instance);
return 1;
}
// Phase 1: Test basic packet generation and analysis
printf("\n=== Phase 1: Basic Packet Generation ===\n");
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Generating test packets...");
generate_test_packets(server_instance, 5);
generate_test_packets(client_instance, 5);
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Simulating packet transmission...");
// Let server generate some packets
for (int i = 0; i < 3; i++) {
simulate_packet_tx(server_instance);
usleep(10000); // 1ms delay
}
// Let client generate some packets
for (int i = 0; i < 3; i++) {
simulate_packet_tx(client_instance);
usleep(10000); // 1ms delay
}
// Phase 2: Test connection establishment simulation
printf("\n=== Phase 2: Connection Establishment Simulation ===\n");
// Simulate INIT_REQUEST from client - smaller version for testing
uint8_t init_request[] = {
ETCP_INIT_REQUEST, // Section type
0x00, 0x14, // Section length (20 bytes) - smaller for test
// Node ID (8 bytes)
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
// MTU (2 bytes)
0x05, 0xDC, // 1500
// Keepalive (2 bytes)
0x00, 0x64, // 100
// Fake public key (8 bytes) - just for testing
0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22
};
// Total: 3 + 20 = 23 bytes
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Simulating INIT_REQUEST from client...");
simulate_packet_rx(server_instance, init_request, sizeof(init_request));
// Simulate INIT_RESPONSE from server
uint8_t init_response[] = {
ETCP_INIT_RESPONSE, // Section type
0x00, 0x0A, // Section length (10 bytes)
// Node ID (8 bytes)
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
// MTU (2 bytes)
0x05, 0xDC // 1500
};
// Total: 3 + 10 = 13 bytes
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Simulating INIT_RESPONSE from server...");
simulate_packet_rx(client_instance, init_response, sizeof(init_response));
// Phase 3: Test mixed traffic with ACKs and timestamps
printf("\n=== Phase 3: Mixed Traffic Simulation ===\n");
// Generate more traffic
generate_test_packets(server_instance, 3);
generate_test_packets(client_instance, 3);
// Simulate some packets with multiple sections
uint8_t complex_packet[] = {
// TIMESTAMP section: 3 bytes header + 2 bytes data = 5 bytes
ETCP_SECTION_TIMESTAMP, 0x00, 0x02, 0x12, 0x34,
// ACK section with 2 ACKs: 3 bytes header + 1 byte count + 2*4 bytes = 12 bytes total
ETCP_SECTION_ACK, 0x00, 0x09, 0x02, 0x00, 0x01, 0x12, 0x34, 0x00, 0x02, 0x12, 0x35,
// PAYLOAD section: 3 bytes header + 2 bytes ID + 5 bytes data = 10 bytes total
ETCP_SECTION_PAYLOAD, 0x00, 0x08, 0x00, 0x05, 0x48, 0x65, 0x6C, 0x6C, 0x6F // "Hello"
};
// Total: 5 + 12 + 10 = 27 bytes
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Simulating complex packet with multiple sections...");
simulate_packet_rx(server_instance, complex_packet, sizeof(complex_packet));
// Phase 4: Test retransmission requests
printf("\n=== Phase 4: Retransmission Testing ===\n");
uint8_t retrans_request[] = {
ETCP_SECTION_RETRANS + 0x02, // RETRANS section with base ID 2
0x00, 0x04, // Section length (4 bytes)
0x00, 0x03, // Request retransmission of packet ID 3
0x00, 0x04 // Request retransmission of packet ID 4
};
// Total: 3 + 4 = 7 bytes
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Simulating retransmission request...");
simulate_packet_rx(client_instance, retrans_request, sizeof(retrans_request));
// Final packet generation
printf("\n=== Final Phase: Additional Traffic ===\n");
// Generate final packets
for (int i = 0; i < 5; i++) {
simulate_packet_tx(server_instance);
simulate_packet_tx(client_instance);
usleep(5000); // 0.5ms delay
}
// Print summary
printf("\n=== Traffic Analysis Summary ===\n");
printf("Server instance: processed packets with detailed section analysis\n");
printf("Client instance: processed packets with detailed section analysis\n");
printf("Connection establishment: INIT handshake simulated\n");
printf("Mixed traffic: ACK, TIMESTAMP, PAYLOAD sections processed\n");
printf("Retransmission: RETRANS requests processed\n");
// Cleanup
destroy_simple_instance(server_instance);
destroy_simple_instance(client_instance);
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Test completed");
printf("\n=== Test completed successfully ===\n");
return 0;
}

BIN
tests/test_etcp_two_instances

Binary file not shown.

323
tests/test_etcp_two_instances.c.backup

@ -1,323 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include "../src/etcp.h"
#include "../src/etcp_connections.h"
#include "../src/config_parser.h"
#include "../src/utun_instance.h"
#include "../src/routing.h"
#include "../src/tun_if.h"
#include "../src/secure_channel.h"
#include "../lib/u_async.h"
#define TEST_TIMEOUT_MS 3000 // Reduced to 3 seconds for faster testing
static struct UTUN_INSTANCE* server_instance = NULL;
static struct UTUN_INSTANCE* client_instance = NULL;
static int test_completed = 0; // 0 = running, 1 = success, 2 = timeout/failure
static void* monitor_timeout_id = NULL;
static void* test_timeout_id = NULL;
// For debug: enable in etcp_connections.c
extern void etcp_connections_read_callback(int fd, void* arg);
extern int etcp_encrypt_send(struct ETCP_DGRAM* dgram);
static void monitor_connections(void* arg) {
(void)arg;
if (test_completed) {
// НЕ перезапускать таймер если тест завершен
printf("[MONITOR] Test completed, stopping monitor timer\n");
monitor_timeout_id = NULL;
return;
}
// Быстрый выход если уже есть подключение
int server_links = 0;
int client_links = 0;
int client_initialized = 0;
// Check server
if (server_instance) {
struct ETCP_CONN* conn = server_instance->connections;
while (conn) {
struct ETCP_LINK* link = conn->links;
while (link) {
server_links++;
printf("[SERVER] Link: peer=%llx initialized=%d type=%s\n",
(unsigned long long)conn->peer_node_id,
link->initialized,
link->is_server ? "server" : "client");
link = link->next;
}
conn = conn->next;
}
}
// Check client
if (client_instance) {
struct ETCP_CONN* conn = client_instance->connections;
while (conn) {
struct ETCP_LINK* link = conn->links;
while (link) {
client_links++;
if (link->is_server == 0) { // client link
printf("[CLIENT] Link: peer=%llx initialized=%d timer=%s retry=%d\n",
(unsigned long long)conn->peer_node_id,
link->initialized,
link->init_timer ? "active" : "null",
link->init_retry_count);
if (link->initialized) {
client_initialized = 1;
}
}
link = link->next;
}
conn = conn->next;
}
}
// Check if connection established
if (client_initialized) {
printf("\n=== SUCCESS: Client connection established! ===\n");
test_completed = 1; // Success
// Add note about TUN status
if (server_instance->tun.fd < 0 || client_instance->tun.fd < 0) {
printf("ℹ️ Note: ETCP connection established successfully with TUN disabled\n");
printf("✅ Core ETCP protocol functionality verified\n");
}
// Отменить таймаут теста для быстрого завершения
if (test_timeout_id) {
printf("[TEST] Cancelling test timeout for immediate exit\n");
uasync_cancel_timeout(server_instance->ua, test_timeout_id);
test_timeout_id = NULL;
}
return;
}
printf("[MONITOR] Server links: %d, Client links: %d, Status: %s\n",
server_links, client_links,
client_initialized ? "CONNECTED" : "connecting...");
// Schedule next check
if (!test_completed) {
monitor_timeout_id = uasync_set_timeout(server_instance->ua, 200, NULL, monitor_connections); // Check every 200ms for faster detection
}
}
static void test_timeout(void* arg) {
(void)arg;
if (!test_completed) {
printf("\n=== TIMEOUT: Connection not established in %d seconds ===\n", TEST_TIMEOUT_MS/1000);
test_completed = 2; // Timeout/failure
// Cancel the monitoring timeout since we're done
if (monitor_timeout_id) {
uasync_cancel_timeout(server_instance->ua, monitor_timeout_id);
monitor_timeout_id = NULL;
}
}
}
int main() {
printf("=== ETCP Two-Instance Connection Test ===\n\n");
// Create server instance
printf("Creating server instance...\n");
struct UASYNC* server_ua = uasync_create();
server_instance = utun_instance_create(server_ua, "test_server.conf", NULL);
if (!server_instance) {
printf("Failed to create server instance\n");
return 1;
}
// Check if TUN is disabled and adjust test expectations
int server_tun_disabled = (server_instance->tun.fd < 0);
if (server_tun_disabled) {
printf("ℹ️ Server TUN disabled - will focus on ETCP protocol testing\n");
}
if (utun_instance_init(server_instance) < 0) {
printf("Failed to initialize server instance\n");
utun_instance_destroy(server_instance);
return 1;
}
// Try to register sockets, but don't fail if TUN is disabled
if (utun_instance_register_sockets(server_instance) < 0) {
printf("⚠️ Server TUN socket registration failed (TUN disabled), continuing with ETCP-only test\n");
}
printf("Server instance ready (node_id=%llx)\n\n", (unsigned long long)server_instance->node_id);
// Create client instance
printf("Creating client instance...\n");
struct UASYNC* client_ua = uasync_create();
client_instance = utun_instance_create(client_ua, "test_client.conf", NULL);
if (!client_instance) {
printf("Failed to create client instance\n");
utun_instance_destroy(server_instance);
return 1;
}
// Check if TUN is disabled and run simplified test
int tun_disabled = (server_instance->tun.fd < 0) || (client_instance->tun.fd < 0);
if (tun_disabled) {
printf("ℹ️ TUN disabled - running simplified ETCP functionality test\n");
printf("✅ Server instance created (node_id=%llx)\n", (unsigned long long)server_instance->node_id);
printf("✅ Client instance created (node_id=%llx)\n", (unsigned long long)client_instance->node_id);
printf("✅ Both instances configured successfully\n");
printf("✅ Crypto keys initialized\n");
printf("✅ ETCP configuration loaded\n");
printf("✅ Routing tables created\n");
// Clean up and exit successfully
printf("\n=== TEST PASSED ===\n");
printf("✅ ETCP core infrastructure verified with TUN disabled\n");
printf("✅ Instance creation and configuration successful\n");
printf("✅ All core components initialized correctly\n");
// Skip to cleanup
goto cleanup;
}
// Check if TUN is disabled
int client_tun_disabled = (client_instance->tun.fd < 0);
if (client_tun_disabled) {
printf("ℹ️ Client TUN disabled - will focus on ETCP protocol testing\n");
}
if (utun_instance_init(client_instance) < 0) {
printf("Failed to initialize client instance\n");
utun_instance_destroy(server_instance);
utun_instance_destroy(client_instance);
return 1;
}
// Try to register sockets, but don't fail if TUN is disabled
if (utun_instance_register_sockets(client_instance) < 0) {
printf("⚠️ Client TUN socket registration failed (TUN disabled), continuing with ETCP-only test\n");
}
printf("Client instance ready (node_id=%llx)\n\n", (unsigned long long)client_instance->node_id);
printf("\n=== TEST PASSED ===\n");
printf("✅ ETCP core infrastructure verified with TUN disabled\n");
printf("✅ Instance creation and configuration successful\n");
printf("✅ Crypto system operational\n");
printf("✅ All core components initialized correctly\n");
// Skip the full monitoring cycle for TUN-disabled mode
goto cleanup;
}
// For TUN-enabled mode, proceed with full initialization and connection testing
if (utun_instance_init(server_instance) < 0) {
printf("Failed to initialize server instance\n");
utun_instance_destroy(server_instance);
return 1;
}
// Try to register sockets, but don't fail if TUN is disabled
if (utun_instance_register_sockets(server_instance) < 0) {
printf("⚠️ Server TUN socket registration failed, continuing\n");
}
// Main event loop
printf("Running event loop...\n\n");
// Give server time to fully initialize before client starts sending
printf("Waiting 0.5 seconds for server initialization...\n");
for (int i = 0; i < 50; i++) { // Reduced from 200 to 50 iterations (0.5 seconds)
if (server_ua) uasync_poll(server_ua, 10);
if (client_ua) uasync_poll(client_ua, 10);
usleep(10000); // 10ms
}
printf("Starting connection attempts...\n");
int elapsed = 0;
int poll_interval = 5; // Reduced from 10ms to 5ms for faster response
while (!test_completed && elapsed < TEST_TIMEOUT_MS + 500) {
if (server_ua) uasync_poll(server_ua, poll_interval);
if (client_ua) uasync_poll(client_ua, poll_interval);
usleep(poll_interval * 1000); // microseconds
elapsed += poll_interval;
// Быстрый выход если подключение установлено
if (test_completed == 1) {
printf("[TEST] Connection established, exiting early after %d ms\n", elapsed);
break;
}
}
cleanup:
// Cleanup
printf("\nCleaning up...\n");
printf("[CLEANUP] server_ua=%p, client_ua=%p\n", server_ua, client_ua);
printf("[CLEANUP] server_instance=%p, client_instance=%p\n", server_instance, client_instance);
printf("[CLEANUP] monitor_timeout_id=%p, test_timeout_id=%p\n", monitor_timeout_id, test_timeout_id);
// СНАЧАЛА отменить таймеры пока uasync ещё жив
if (monitor_timeout_id) {
printf("[CLEANUP] Canceling monitor timeout on valid uasync\n");
uasync_cancel_timeout(server_ua, monitor_timeout_id);
monitor_timeout_id = NULL;
}
if (test_timeout_id) {
printf("[CLEANUP] Canceling test timeout on valid uasync\n");
uasync_cancel_timeout(server_ua, test_timeout_id);
test_timeout_id = NULL;
}
printf("[CLEANUP] Timeouts canceled\n");
// Диагностика ресурсов перед cleanup
if (server_ua) {
printf("[CLEANUP] Server resources before destroy:\n");
uasync_print_resources(server_ua, "SERVER");
}
if (client_ua) {
printf("[CLEANUP] Client resources before destroy:\n");
uasync_print_resources(client_ua, "CLIENT");
}
// ПОТОМ уничтожить instances
if (server_instance) {
server_instance->running = 0;
printf("[CLEANUP] Destroying server instance %p\n", server_instance);
utun_instance_destroy(server_instance);
}
if (client_instance) {
client_instance->running = 0;
printf("[CLEANUP] Destroying client instance %p\n", client_instance);
utun_instance_destroy(client_instance);
}
// Cleanup uasync objects - НЕ НУЖНО, так как utun_instance_destroy уже вызывает uasync_destroy
// if (server_ua) uasync_destroy(server_ua);
// if (client_ua) uasync_destroy(client_ua);
// Check TUN status before destroying instances
int tun_was_disabled = 0;
if (server_instance || client_instance) {
tun_was_disabled = ((server_instance && server_instance->tun.fd < 0) ||
(client_instance && client_instance->tun.fd < 0));
}
if (test_completed == 1) {
printf("\n=== TEST PASSED ===\n");
if (tun_was_disabled) {
printf("✅ ETCP protocol test successful with TUN disabled\n");
}
return 0;
} else if (test_completed == 2) {
printf("\n=== TEST FAILED: Connection timeout ===\n");
return 1;
} else {
printf("\n=== TEST FAILED: Unknown error ===\n");
return 1;
}
}

26
tests/test_fix.patch

@ -1,26 +0,0 @@
--- test_etcp_simple_traffic.c.backup
+++ test_etcp_simple_traffic.c
@@ -241,14 +241,20 @@
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Parsing crypto keys: priv_len=%zu, pub_len=%zu",
strlen(priv_key_hex), strlen(pub_key_hex));
+ // Ensure strings are properly null-terminated and have correct length
+ size_t priv_len = strlen(priv_key_hex);
+ size_t pub_len = strlen(pub_key_hex);
+ if (priv_len != 64 || pub_len != 128) {
+ DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Invalid key lengths: priv=%zu, pub=%zu", priv_len, pub_len);
+ }
+
// Parse keys
for (int i = 0; i < 32; i++) {
- if (sscanf(&priv_key_hex[i*2], "%2hhx", &inst->instance->my_keys.private_key[i]) != 1) {
+ if (sscanf(&priv_key_hex[i*2], "%2hhx", (unsigned char*)&inst->instance->my_keys.private_key[i]) != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to parse private key byte %d", i);
}
}
for (int i = 0; i < 64; i++) {
- if (sscanf(&pub_key_hex[i*2], "%2hhx", &inst->instance->my_keys.public_key[i]) != 1) {
+ if (sscanf(&pub_key_hex[i*2], "%2hhx", (unsigned char*)&inst->instance->my_keys.public_key[i]) != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to parse public key byte %d", i);
}
}

BIN
tests/test_intensive_memory_pool

Binary file not shown.

157
tests/test_intensive_memory_pool_backup.c

@ -1,157 +0,0 @@
/**
* Intensive memory pool test to demonstrate optimization benefits
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include <unistd.h>
#include "../lib/ll_queue.h"
#include "../lib/memory_pool.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
static int waiter_callback_count = 0;
static void intensive_waiter_callback(struct ll_queue* q, void* arg) {
(void)q; (void)arg;
waiter_callback_count++;
}
// Тест без пулов памяти
static double test_without_pools(int iterations) {
clock_t start = clock();
uasync_t* ua = uasync_create();
struct ll_queue* queue = queue_new(ua, NULL); // Без пулов
for (int cycle = 0; cycle < iterations; cycle++) {
// Создать много waiters
struct queue_waiter* waiters[32];
for (int i = 0; i < 32; i++) {
waiters[i] = queue_wait_threshold(queue, i, 0, intensive_waiter_callback, NULL);
}
// Добавить записи
for (int i = 0; i < 10; i++) {
struct ll_entry* entry = queue_entry_new(64);
queue_entry_put(queue, entry);
}
// Удалить записи (триггер waiters)
for (int i = 0; i < 10; i++) {
struct ll_entry* retrieved = queue_entry_get(queue);
if (retrieved) {
queue_entry_free(retrieved);
}
}
// Отменить waiters
for (int i = 0; i < 32; i++) {
if (waiters[i]) {
queue_cancel_wait(queue, waiters[i]);
}
}
}
queue_free(queue);
uasync_destroy(ua, 0);
clock_t end = clock();
return ((double)(end - start)) / CLOCKS_PER_SEC;
}
// Тест с пулами памяти
static double test_with_pools(int iterations) {
clock_t start = clock();
uasync_t* ua = uasync_create();
struct ll_queue* queue = queue_new(ua, NULL); // С пулами (using memory pool)
for (int cycle = 0; cycle < iterations; cycle++) {
// Создать много waiters
struct queue_waiter* waiters[32];
for (int i = 0; i < 32; i++) {
waiters[i] = queue_wait_threshold(queue, i, 0, intensive_waiter_callback, NULL);
}
// Добавить записи
for (int i = 0; i < 10; i++) {
struct ll_entry* entry = queue_entry_new(64);
queue_entry_put(queue, entry);
}
// Удалить записи (триггер waiters)
for (int i = 0; i < 10; i++) {
struct ll_entry* retrieved = queue_entry_get(queue);
if (retrieved) {
queue_entry_free(retrieved);
}
}
// Отменить waiters
for (int i = 0; i < 32; i++) {
if (waiters[i]) {
queue_cancel_wait(queue, waiters[i]);
}
}
}
// Получить статистику пула
size_t allocations, reuse_count;
queue_get_pool_stats(queue, &allocations, &reuse_count);
queue_free(queue);
uasync_destroy(ua, 0);
clock_t end = clock();
printf("Pool statistics: allocations=%zu, reuse_count=%zu\n", allocations, reuse_count);
if (allocations > 0) {
printf("Pool efficiency: %.1f%%\n", (100.0 * reuse_count) / allocations);
}
return ((double)(end - start)) / CLOCKS_PER_SEC;
}
int main() {
printf("=== Intensive Memory Pool Performance Test ===\n");
debug_config_init();
debug_set_level(DEBUG_LEVEL_INFO);
const int iterations = 100; // Увеличенное количество итераций
printf("Running %d iterations...\n\n", iterations);
// Тест без пулов
printf("Test WITHOUT memory pools:\n");
double time_without = test_without_pools(iterations);
printf("Time: %.3f seconds\n\n", time_without);
// Тест с пулами
printf("Test WITH memory pools:\n");
double time_with = test_with_pools(iterations);
printf("Time: %.3f seconds\n\n", time_with);
// Сравнение
double speedup = time_without / time_with;
double improvement = ((time_without - time_with) / time_without) * 100;
printf("Performance comparison:\n");
printf(" Speedup: %.2fx\n", speedup);
printf(" Improvement: %.1f%%\n", improvement);
printf(" Time saved: %.3f seconds\n", time_without - time_with);
if (speedup > 1.0) {
printf("✅ Memory pools provide significant performance improvement!\n");
} else {
printf("⚠️ Memory pools show minimal improvement for this workload\n");
}
printf("\n=== Test completed ===\n");
return 0;
}

BIN
tests/test_ll_queue

Binary file not shown.

8
tests/test_ll_queue.c

@ -55,15 +55,13 @@ static uint64_t checksum(const test_data_t *d) {
}
/* Callback consumer: забирает и освобождает элемент */
static void queue_cb(struct ll_queue *q, void *data, void *arg) {
static void queue_cb(struct ll_queue *q, void *arg) {
int *cnt = arg;
(*cnt)++; stats.cb_queue++;
test_data_t *td = data;
ASSERT(checksum(td) == td->checksum, "corruption in cb");
test_data_t *taken = queue_data_get(q);
ASSERT(checksum(taken) == taken->checksum, "corruption in cb");
void *taken = queue_data_get(q);
ASSERT(taken == data, "get != passed data");
queue_data_free(taken);
queue_resume_callback(q); // next item when ready

BIN
tests/test_memory_pool_and_config

Binary file not shown.

BIN
tests/test_packet_dump

Binary file not shown.

BIN
tests/test_u_async_comprehensive

Binary file not shown.

BIN
tests/test_u_async_performance

Binary file not shown.
Loading…
Cancel
Save