Browse Source

Fix: ETCP module compilation errors and build system issues

- Added missing structure members to ETCP_CONN (ack_packets_count, last_rx_ack_id, rtt_history, rtt_history_idx, total_packets_sent)
- Added missing bandwidth member to ETCP_LINK structure
- Fixed include path for ll_queue.h header
- Added math library linking (-lm) to build system
- Added etcp_loadbalancer.c to build sources
- Fixed forward declarations and function calls in ETCP modules
- Updated test dependencies to include loadbalancer object files

All simple compilation errors resolved, project builds successfully and tests pass (except TUN permission test which is expected in container).
nodeinfo-routing-update
Evgeny 8 months ago
parent
commit
e048b10899
  1. 6
      AGENTS.md
  2. 161
      build_errors.txt
  3. 118
      doc/etcp_protocol.txt
  4. 479
      lib/ll_queue.c
  5. 829
      lib/ll_queue.c2
  6. 847
      lib/ll_queue.c3
  7. 744
      lib/ll_queue.c_
  8. 78
      lib/ll_queue.h
  9. 224
      lib/ll_queue.h2
  10. 224
      lib/ll_queue.h3
  11. 210
      lib/ll_queue.h_
  12. 4
      src/Makefile.am
  13. 11
      src/config_parser.c
  14. 1
      src/config_parser.h
  15. 928
      src/etcp.c
  16. 467
      src/etcp.c1
  17. 489
      src/etcp.c_
  18. 170
      src/etcp.h
  19. 2
      src/etcp_connections.h
  20. 95
      src/etcp_loadbalancer.c
  21. 20
      src/etcp_loadbalancer.h
  22. 63
      src/etcp_protocol.txt
  23. 6
      src/routing.c
  24. 4
      src/tun_if.c
  25. 3
      src/tun_if.h
  26. 39
      src/utun.c
  27. 85
      src/utun_instance.c
  28. 2
      test_client.conf
  29. 2
      test_server.conf
  30. 16
      tests/Makefile.am
  31. BIN
      tests/test_crypto
  32. BIN
      tests/test_etcp_crypto
  33. BIN
      tests/test_etcp_two_instances
  34. 407
      tests/test_ll_queue_pos.c
  35. 752
      tests/test_ll_queue_unified.c

6
AGENTS.md

@ -133,6 +133,7 @@ sc_decrypt(&ctx, ciphertext, ciphertext_len, plaintext, &plaintext_len);
│ ├── secure_channel.c # Cryptography
│ └── tun_if.c # TUN/TAP interface
├── tests/ # Test programs
├── doc/ # Technical Specifications
├── tinycrypt/ # TinyCrypt crypto library
└── net_emulator/ # Network emulator
```
@ -211,4 +212,9 @@ Crypto: Fixed CCM nonce size to 13 bytes, all crypto tests passing
- нельзя ничего восстанавливать из репозитория не прашивая
- Когда пишешь код всегда загружай в контекст и мысленно разберись как работают все функции/струтуры, назначение переменных которые используешь.
тех задания для реализации в каталоге /doc.
/doc/etcp_protocol.txt - основной протокол (похож на TCP+QUIC, поддеиживает шифрования, load balancing multi-link, работу а неустойчивых каналах, утилизацию полосы и недопущение перегрузки каналов связи)
- реализация в /src/etcp*.c/h
*Last updated: 2025-01-20 - After full crypto implementation and testing*

161
build_errors.txt

@ -0,0 +1,161 @@
ar: модификатор «u» игнорируется, так как по умолчанию используется «D» (смотрите «U»)
utun_instance.c: In function ‘utun_instance_create’:
utun_instance.c:93:49: warning: ‘/32’ directive output may be truncated writing 3 bytes into a region of size between 1 and 64 [-Wformat-truncation=]
93 | 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:93:5:
/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 ());
| ~~~~~~~~~~~~~~~~~
etcp.c: In function ‘etcp_conn_reset’:
etcp.c:175:9: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history_idx’
175 | etcp->rtt_history_idx = 0;
| ^~
etcp.c:176:16: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’
176 | memset(etcp->rtt_history, 0, sizeof(etcp->rtt_history));
| ^~
etcp.c:176:45: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’
176 | memset(etcp->rtt_history, 0, sizeof(etcp->rtt_history));
| ^~
etcp.c: In function ‘etcp_request_pkt’:
etcp.c:251:47: error: ‘input_queue_cb’ undeclared (first use in this function)
251 | queue_set_callback(etcp->input_queue, input_queue_cb, etcp); // Define cb to resume
| ^~~~~~~~~~~~~~
etcp.c:251:47: note: each undeclared identifier is reported only once for each function it appears in
etcp.c:288:30: warning: implicit declaration of function ‘etcp_loadbalancer_select_link’ [-Wimplicit-function-declaration]
288 | struct ETCP_LINK* link = etcp_loadbalancer_select_link(etcp);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
etcp.c:288:30: warning: initialization of ‘struct ETCP_LINK *’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
etcp.c: In function ‘append_optional_sections’:
etcp.c:391:13: error: ‘struct ETCP_CONN’ has no member named ‘ack_packets_count’
391 | etcp->ack_packets_count++;
| ^~
etcp.c: In function ‘process_section_ack’:
etcp.c:437:11: error: ‘struct ETCP_CONN’ has no member named ‘last_rx_ack_id’; did you mean ‘last_rx_id’?
437 | etcp->last_rx_ack_id = (data[1 + (count-1)*4] << 8) | data[2 + (count-1)*4]; // Last ACK ID
| ^~~~~~~~~~~~~~
| last_rx_id
etcp.c: In function ‘update_link_bandwidth’:
etcp.c:642:9: error: ‘struct ETCP_LINK’ has no member named ‘bandwidth’
642 | link->bandwidth = (link->bandwidth * 0.9) + (new_bw * 0.1); // Smooth
| ^~
etcp.c:642:28: error: ‘struct ETCP_LINK’ has no member named ‘bandwidth’
642 | link->bandwidth = (link->bandwidth * 0.9) + (new_bw * 0.1); // Smooth
| ^~
etcp.c: In function ‘update_rtt’:
etcp.c:652:9: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’
652 | etcp->rtt_history[etcp->rtt_history_idx++] = new_rtt;
| ^~
etcp.c:652:27: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history_idx’
652 | etcp->rtt_history[etcp->rtt_history_idx++] = new_rtt;
| ^~
etcp.c:653:13: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history_idx’
653 | if (etcp->rtt_history_idx >= RTT_HISTORY_SIZE) etcp->rtt_history_idx = 0;
| ^~
etcp.c:653:56: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history_idx’
653 | if (etcp->rtt_history_idx >= RTT_HISTORY_SIZE) etcp->rtt_history_idx = 0;
| ^~
etcp.c: In function ‘calculate_jitter’:
etcp.c:661:17: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’
661 | if (etcp->rtt_history[i] == 0) continue;
| ^~
etcp.c:662:17: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’
662 | if (etcp->rtt_history[i] < min_rtt) min_rtt = etcp->rtt_history[i];
| ^~
etcp.c:662:59: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’
662 | if (etcp->rtt_history[i] < min_rtt) min_rtt = etcp->rtt_history[i];
| ^~
etcp.c:663:17: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’
663 | if (etcp->rtt_history[i] > max_rtt) max_rtt = etcp->rtt_history[i];
| ^~
etcp.c:663:59: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’
663 | if (etcp->rtt_history[i] > max_rtt) max_rtt = etcp->rtt_history[i];
| ^~
etcp.c: In function ‘etcp_get_stats’:
etcp.c:684:43: error: ‘struct ETCP_CONN’ has no member named ‘total_packets_sent’
684 | if (packets_sent) *packets_sent = etcp->total_packets_sent;
| ^~
make[2]: *** [Makefile:605: utun-etcp.o] Ошибка 1
make[1]: *** [Makefile:375: all-recursive] Ошибка 1
make: *** [Makefile:316: all] Ошибка 2
etcp.c: In function ‘etcp_conn_reset’:
etcp.c:175:9: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history_idx’
175 | etcp->rtt_history_idx = 0;
| ^~
etcp.c:176:16: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’
176 | memset(etcp->rtt_history, 0, sizeof(etcp->rtt_history));
| ^~
etcp.c:176:45: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’
176 | memset(etcp->rtt_history, 0, sizeof(etcp->rtt_history));
| ^~
etcp.c: In function ‘etcp_request_pkt’:
etcp.c:251:47: error: ‘input_queue_cb’ undeclared (first use in this function)
251 | queue_set_callback(etcp->input_queue, input_queue_cb, etcp); // Define cb to resume
| ^~~~~~~~~~~~~~
etcp.c:251:47: note: each undeclared identifier is reported only once for each function it appears in
etcp.c:288:30: warning: implicit declaration of function ‘etcp_loadbalancer_select_link’ [-Wimplicit-function-declaration]
288 | struct ETCP_LINK* link = etcp_loadbalancer_select_link(etcp);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
etcp.c:288:30: warning: initialization of ‘struct ETCP_LINK *’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
etcp.c: In function ‘append_optional_sections’:
etcp.c:391:13: error: ‘struct ETCP_CONN’ has no member named ‘ack_packets_count’
391 | etcp->ack_packets_count++;
| ^~
etcp.c: In function ‘process_section_ack’:
etcp.c:437:11: error: ‘struct ETCP_CONN’ has no member named ‘last_rx_ack_id’; did you mean ‘last_rx_id’?
437 | etcp->last_rx_ack_id = (data[1 + (count-1)*4] << 8) | data[2 + (count-1)*4]; // Last ACK ID
| ^~~~~~~~~~~~~~
| last_rx_id
etcp.c: In function ‘update_link_bandwidth’:
etcp.c:642:9: error: ‘struct ETCP_LINK’ has no member named ‘bandwidth’
642 | link->bandwidth = (link->bandwidth * 0.9) + (new_bw * 0.1); // Smooth
| ^~
etcp.c:642:28: error: ‘struct ETCP_LINK’ has no member named ‘bandwidth’
642 | link->bandwidth = (link->bandwidth * 0.9) + (new_bw * 0.1); // Smooth
| ^~
etcp.c: In function ‘update_rtt’:
etcp.c:652:9: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’
652 | etcp->rtt_history[etcp->rtt_history_idx++] = new_rtt;
| ^~
etcp.c:652:27: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history_idx’
652 | etcp->rtt_history[etcp->rtt_history_idx++] = new_rtt;
| ^~
etcp.c:653:13: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history_idx’
653 | if (etcp->rtt_history_idx >= RTT_HISTORY_SIZE) etcp->rtt_history_idx = 0;
| ^~
etcp.c:653:56: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history_idx’
653 | if (etcp->rtt_history_idx >= RTT_HISTORY_SIZE) etcp->rtt_history_idx = 0;
| ^~
etcp.c: In function ‘calculate_jitter’:
etcp.c:661:17: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’
661 | if (etcp->rtt_history[i] == 0) continue;
| ^~
etcp.c:662:17: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’
662 | if (etcp->rtt_history[i] < min_rtt) min_rtt = etcp->rtt_history[i];
| ^~
etcp.c:662:59: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’
662 | if (etcp->rtt_history[i] < min_rtt) min_rtt = etcp->rtt_history[i];
| ^~
etcp.c:663:17: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’
663 | if (etcp->rtt_history[i] > max_rtt) max_rtt = etcp->rtt_history[i];
| ^~
etcp.c:663:59: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’
663 | if (etcp->rtt_history[i] > max_rtt) max_rtt = etcp->rtt_history[i];
| ^~
etcp.c: In function ‘etcp_get_stats’:
etcp.c:684:43: error: ‘struct ETCP_CONN’ has no member named ‘total_packets_sent’
684 | if (packets_sent) *packets_sent = etcp->total_packets_sent;
| ^~
make[2]: *** [Makefile:605: utun-etcp.o] Ошибка 1
make[1]: *** [Makefile:375: all-recursive] Ошибка 1
make: *** [Makefile:316: all] Ошибка 2
make[1]: *** Нет правила для сборки цели «../src/utun-secure_channel.o», требуемой для «test_etcp_crypto». Останов.
make: *** [Makefile:1024: check-am] Ошибка 2

118
doc/etcp_protocol.txt

@ -0,0 +1,118 @@
Формат кодограммы:
------------------
пакеты идут по следующему пути:
пакет кем-то помещается в входную очередь (ETCP input_queue).
Когда load balancer готов отправить следующий пакет он делает вызов etcp_request_pkt в etcp.c/h и etcp либо формирует следующую кодограмму или встаёт в состояние async ожидания (возвращая null):
- либо wait ETCP input_queue (если нечего передавать)
- либо wait SACK + wait timeout (если sack очередь полная и нет необслуженных запросов ретрансмиссий). после истечения wait timeout он помечает используя round-robin очередной пакет как "нужна ретрансмиссия".
из async ожидания он может вызвать etcp_loadbalancer input_from_etcp и передать пакет на отправку.
как формируется пакет:
1. берем пакет из списка неподтвержденных пакетов ожидающих отправку
2. если есть место в rwin (объём inflight данных) то берем очередной пакет из ETCP input_queue и его отправляем
если пакет найден:
1. вызываем функцию формирования опциональных секций (ACK, RETRANS, channel timestamp) - они записываются в начало
2. в конец добавляем секцию 0x00 с данными пакета
при отправке пакета помечаем в inflight списке с какого интерфейса он отправлен
Функция прикрепления опциональных секций:
- запрашивает выбор канала передачи.
- добавляет накопившиеся ACK и RETRANS
При приёме пакета (от etcp_connections):
- последовательно сканируем секции и отдаём их на обработку нужным обработчикам:
- retrans: помечаем секции как нужна ретрансмиссия. также ставим флаг в конце запустить etcp если он в wait timeout, т.е. wait_timeout!=NULL
- ack: помечаем в inflight пакеты как подтверждённые и проставляем время подтверждения. также и ставим флаг в конце запустить etcp если он в wait timeout, т.е. wait_timeout!=NULL
- timestamp: обновляем last RTT, пересчитываем RTT10,RTT100 (плавающим окном за последние 10/100 пакетов), и jitter=max(last 10)-min(last 10 packets)
- [0x00] payload: добавляем пакет в нужное место сборочного linked-list (сверяем по ID если дубликат - игнорируем).
после сканирования проверяем появились ли в linked-list собранные данные которые можно переместить в выходную очередь. и перемещаем если есть.
пересканирование inflight:
- вызывается по таймеру, таймер заводится при добавлении пакета в пустой inflight.
- если время последней отправки > RTT10*k1+jitter*k2 (коэффициенты вынести в настройки utun instance) то пакет заново отправляется
и заводим таймер отправки следующей ретрансмиссии (список отсортирован по времени)
- если встречаем успешный ack, запоминаем это и его timestamp (назовем lp_ts).
- если после этого далее в очереди находим пакет который не имеет переповторов и timestamp отправки раньше чем [ИСПРАВЛЕНО: фраза обрезана в оригинале; предположительно, "раньше чем lp_ts" на основе контекста; если нет, пометить как неполное]
inflight - два списка (ll_queue):
- список с неподтвержденными пакетами ожидающими ack (при запросе ретрансмиссии пакет перемещается в ожидающие отправку)
- список с неподтвержденными пакетами ожидающими отправку (при отправке возвращается в ожидающие ack)
доп. свойства пакетов:
- указатель на ETC_LINK последней отправки
- timestamp последней отправки
- число отправок
- число запросов переповтора
Размер буфера inflight лимитируем как сумму по всем активным линкам optimal_inflight.
optimal_inflight расчитывается из RTT и bandwidth линка.
bandwidth по каждому линку адаптивно подстраиваем следующим алгоритмом:
периодически инициируем burst_transmission (напоминаем ID пакета на которой она началась).
после передаём пачку пакетов только по этому линку со следующими таймингами:
<pkt1> <delay 4x> <pkt2><pkt3><pkt4><pkt5> - т.е. чуть задерживаем канал а потом выплёвываем пачку пакетов.
и добавляем опцию [meas_ts][n] - где n - номер пакета в пачке
приёмная сторона видя эту секцию должна отправить секцию [meas_resp] по всем пакетам, которые содержат локальный RECV_Timestamp фиксирующий время приёма каждого пакета с [meas_ts].
приняв эти response анализируем по разнице между pkt1-pkt2 не забита ли очередь отправки, а по дельте pkt2-3-4-5 оцениваем bandwidth.
дальше по алгоритму (который потом оттюним) корректируем transmitter speed limit.
**** Формат кодограмм для etcp.c/h ****
Каждая кодограмма состоит из обязательного заголовка и опциональных секций:
1. Обязательный заголовок всего пакета (2 байта) добавляется при передаче, есть во всех пакетах:
[Timestamp high][Timestamp low]
- Timestamp: время отправки в единицах 0.1 мс (циклическое). при переповторах время обновляется
шифруется строго ВСЁ включая ВСЕ заголовки (кроме INIT+publickey). timestamp вставляется ВСЕГДА ВО ВСЕ КОДОГРАММЫ
2. Опциональные секции (одна или несколько) добавляются в etcp.c:
а) Подтверждения (ACK) - заголовок 0x01:
[0x01][count][(id_hi,id_lo,ts_hi,ts_lo)×count][last_delivered_hi,lo][last_rx_hi,lo]
- count: количество пар ID+timestamp (1-32)
- last_delivered_id: последний ID, доставленный получателю
- last_rx_id: последний полученный ID (для синхронизации прогресса)
б) Запросы ретрансмиссии (RETRANS) - заголовок 0x10-0x2F:
[0x10+(count-1)][(id_hi,id_lo)×count][last_delivered_hi,lo][last_rx_hi,lo]
- count: (заголовок & 0x0F) + 1 (1-32)
- last_delivered_id, last_rx_id: для синхронизации состояния
в) channel timestamp (добавляется после выбора канала передачи):
[0x0F] [RET_Timestamp high][RET_Timestamp low] [RECV_Timestamp high][RECV_Timestamp low]
- RET_Timestamp: это timestamp последнего принятого пакета по этому каналу ETCP_LINK плюс разница времени между принятием этого пакета и текущим временем. т.е. приёмная сторона по этому timestamp (и зная своё время) может просчитать время в пути туда + обратно
- RECV_Timestamp: это timestamp времени приёма последнего принятого пакета (т.е. отправляем время "когда мы получили этот пакет по локальном урмени") - по нему другая сторона сможет просчитать относительное время задержки в одну сторону (время туда без обратно).
если последний пакет по этому каналу принят сильно давно (прошло более 30000 единиц времени) то channel timestamp не добавляем. [СПОРНО: порог 30000 (3 сек) arbitrary; может потребовать настройки или обоснования; также риск clock skew между нодами]
3. Полезная нагрузка - заголовок 0x00 (одна секция и всегда последняя):
[0x00] [ID high][ID low] [данные...] - Данные пакета
- ID: 16-битный циклический номер пакета. [СПОРНО: 16-bit может привести к быстрому overflow в long-lived сессиях; рассмотреть 32-bit]
Пакет может содержать несколько опциональных секций (например, ACK + данные). [СПОРНО: нет явных length полей для секций; parsing implicit, риск ошибок при variable длине; добавить length?]
цель протокола:
1. контролировать состояние очередей (не накапливать данные где этого можно избежать), анализировать RTT и inflight - динамически подстраивать размер окна и ретрансмиссии, утилизируя сеть насколько это возможно но не допуская наполнения буферов в сети (что приводит к бестолковому увеличению RTT)
Установка подключения для канала (реализуется в etcp_connections.c/h):
каждое ETCP подключение может содержать несколько линков до endpoint (разные маршруты, каналы связи).
При прохождении трафика через этот модуль подсчитываются метрики (для каждого канала отдельно):
- RTT последнего пакета и время отправки последнего пакета
- пакеты делятся на 3 категории: <300 байт, 300-699 байт, 700+ байт. для каждой категории отдельно считаются метрики:
- количество отправленных в пакетах байтах
- количество перезапросов (по каждому каналу отдельно) в пакетах и байтах. когда etcp модуль принял запрос ретрансмиссии он смотрит с какого интерфейса был отправлен этот пакет (это запоминается при отправке) и увеличивает счетчики.
- обновление текущего количества неподтвержденных данных inflight (в пакетах и байтах) - сколько по этому каналу отправленных и неподтвержденных на текущий момент
Планировщик/балансир каналов (etcp_loadbalancer.c/h):
для каждого канала ограничение полосы с тестированием отклика:
- полосу ограничиваем по объему данных - полному числу байт ethernet пакта (добавляем размер заголовков)
Ограничение реализуем следующим образом:
- в структуре есть счётчик времени (квантизация стандартная 0.1ms) по которое канал загружен данными. И суб-разряды (nanotime) этого счетчика 0.1ns - 0.1ms (считает по модулю 1000000 и переполнения добавляет в основной счетчик) чтобы при высоких скоростях не было потери точности (т.е. было ненулевое время передачи одного байта).
Этим счётчиком контролируется полоса: при передаче к нему добавляется расчётное время передачи текущего ethernet пакета (используя текущий bandwidth считаем время передачи одного байта (float) и умножаем на число байт, переводим в таймбазу nanotime). А если при передаче время меньше current_time - delta_time (delta_time - константа в define, x0.1ms) то он устанавливается как current time-delta_time. (при неактивности обновляем время)
счетчик нужен для контроля можно ли передавать следующий пакет ("расчётное время передачи"); [СПОРНО: сложность с float; риск precision issues]
когда etcp пытается отправить пакет через линк, то если в линке не инициализировано подключение и он клиент - то он отбрасывает этот пакет и запускает процесс установки соединения.
также процесс установки соединения инициируется при добавлении канала (в etcp_link_new) если это client.
процесс установки соединения:
- отправляем init запрос и выставляем таймаут (сохраняем его в struct ETCP_LINK)
- по таймауту повторяем init запросы, ведем счетчик запросов init.
- когда получаем init подтверждение (отправляется в etcp_connections_read_callback) снимаем таймаут и выставляем флаг struct ETCP_LINK initialized=1.
При добавлении нового канала посылаем INIT 0x04 (без сброса), а при первичной инициализации подключения - 0x02 (это нужно, т.к. например перезапуск локального софта должен инициировать перезапуск удаленного соединения).
**** Формат кодограмм для etcp_connections.c/h ****
Кодограммы с этими секциями обрабатываются в etcp_connections (в этих кодограммах всегда только одна секция). в обязательном заголовке ID не используется, при передаче для порядка =0: [СПОРНО: заголовок в etcp — только 2 байта TS; здесь подразумевается ID? Уточнить единый формат]
1) Init запрос - заголовок 0x02 (со сбросом etcp сессии) или 0x04 (без сброса):
[0x02/0x04] [my_node_id 64bit] [my mtu high] [my mtu low] [keepalive high] [keepalive low] [my public key (64 байта, не шифруется)]
- Инициирует новый connection для tcp instance. если tcp instance нет (первое подключение) - создаёт. Между нодами только одно подключение, но можно добавлять каналы.
- Публичный ключ отправляется в конце пакета без шифрования, чтобы получатель мог установить его и расшифровать остальную часть пакета (т.к. инициатор соединения всегда имеет оригинальный peer public key в конфиге - по нему исключаем MITM)
2) Init подтверждение - заголовок 0x03/0x05 (ответы соответственно на коды 0x02 и 0x04):
[0x03/0x05] [my_node_id 64bit] [my mtu high] [my mtu low]
- Подтверждение инициализации (канал успешно создан, можно начинать обмен)
При получении init получатель пакета должен:
- reset ETCP_LINK с этим ip_port если он есть
- создать новый ETCP_CONN с этим node_id. если уже существует подключение с этим node_id - вызвать etcp_reset (функция сброса окон неподтвержденных данных и нумерации)
приём кодограмм из UDP выглядит так:
uasync select -> etcp_connection.c etcp_connections_read_callback: memory_pool_alloc, decrypt (or init) -> etcp_conn_input (etcp.c/h - сам tcp механизм) -> ETCP_CONN output_queue

479
lib/ll_queue.c

@ -1,3 +1,4 @@
// ll_queue.c
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
@ -16,6 +17,10 @@ static void check_waiters(struct ll_queue* q) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: checking waiters, count=%d, bytes=%zu",
q->count, q->total_bytes);
// Сначала собрать список коллбэков для вызова, чтобы избежать проблем с модификацией списка во время итерации
struct queue_waiter* to_call = NULL;
struct queue_waiter* to_call_tail = NULL;
struct queue_waiter** pprev = &q->waiters;
struct queue_waiter* waiter = q->waiters;
@ -28,15 +33,16 @@ static void check_waiters(struct ll_queue* q) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: condition met, calling callback, count=%d<=%d, bytes=%zu<=%zu (max_bytes_check=%s)",
q->count, waiter->max_packets, q->total_bytes, waiter->max_bytes,
waiter->max_bytes == 0 ? "disabled" : "enabled");
waiter->callback(q, waiter->callback_arg);
// Удалить waiter из списка
*pprev = next;
if (q->pool) {
memory_pool_free(q->pool, waiter);
// Добавить в список для вызова
waiter->next = NULL;
if (to_call_tail) {
to_call_tail->next = waiter;
} else {
free(waiter);
to_call = waiter;
}
// pprev уже указывает на правильный следующий элемент
to_call_tail = waiter;
} else {
// Условие не выполнено - оставить в списке
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: condition NOT met, count=%d>%d or bytes=%zu>%zu (max_bytes_check=%s)",
@ -46,11 +52,60 @@ static void check_waiters(struct ll_queue* q) {
}
waiter = next;
}
// Теперь вызвать коллбэки
waiter = to_call;
while (waiter) {
struct queue_waiter* next = waiter->next;
waiter->callback(q, waiter->callback_arg);
if (q->pool) {
memory_pool_free(q->pool, waiter);
} else {
free(waiter);
}
waiter = next;
}
}
// ==================== Унифицированная система версий позиций ====================
// Унифицированная проверка версии позиции с автоматическим пересканированием
static int queue_pos_ensure_valid(struct ll_queue_pos* pos) {
if (!pos || !pos->queue) return 0;
// Если позиция уже валидна и версия совпадает, всё хорошо
if (pos->valid && pos->version == pos->queue->version) {
return 1;
}
// Несовпадение версии или позиция невалидна - нужно пересканировать
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "Position version %lu != queue version %lu, rescanning...",
(unsigned long)pos->version, (unsigned long)pos->queue->version);
// Сохранить целевой индекс (где мы хотим быть)
int target_index = pos->index;
// Обновить версию позиции до текущей
pos->version = pos->queue->version;
// Обработать краевой случай: индекс больше не существует из-за изменения размера очереди
if (target_index >= pos->queue->count) {
// Позиция выходит за пределы очереди - инвалидируем её
pos->valid = 0;
return 0;
}
// Простой подход: сбросить и перейти к тому же индексу
// Это найдёт элемент, который теперь находится в целевом индексе
queue_pos_reset(pos);
return queue_pos_seek(pos, target_index);
}
// ==================== Управление очередью ====================
struct ll_queue* queue_new(struct UASYNC* ua, struct memory_pool* pool) {
if (!ua) return NULL;
struct ll_queue* q = calloc(1, sizeof(struct ll_queue));
if (!q) return NULL;
@ -66,6 +121,7 @@ struct ll_queue* queue_new(struct UASYNC* ua, struct memory_pool* pool) {
q->ua = ua;
q->waiters = NULL;
q->pool=pool;
q->version = 0;
return q;
}
@ -83,6 +139,7 @@ void queue_free(struct ll_queue* q) {
struct ll_entry* next = entry->next;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: releasing entry %p (entry %d), ref_count=%d",
entry, entry_count++, entry->ref_count);
entry->ref_count--; // Дополнительно уменьшить за присутствие в очереди
queue_entry_free(entry); // Это уменьшит ref_count и освободит только если ref_count == 0
entry = next;
}
@ -161,6 +218,7 @@ struct ll_entry* queue_entry_new(size_t data_size) {
if (!entry) return NULL;
entry->next = NULL;
entry->prev = NULL; // Инициализировать prev для двусвязного списка
entry->size = data_size;
entry->ref_count = 1; // Начальный счетчик ссылок
// Область данных оставить неинициализированной для производительности
@ -211,12 +269,14 @@ int queue_entry_put(struct ll_queue* q, struct ll_entry* entry) {
// Увеличить счетчик ссылок при добавлении в очередь
entry->ref_count++;
// Добавить в хвост (FIFO)
// Добавить в хвост (FIFO) - двусвязный список
entry->next = NULL;
entry->prev = q->tail;
if (q->tail) {
q->tail->next = entry;
} else {
q->head = entry;
q->head = entry; // Очередь была пустой
}
q->tail = entry;
q->count++;
@ -225,6 +285,9 @@ int queue_entry_put(struct ll_queue* q, struct ll_entry* entry) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: added entry %p, new count=%d, total_bytes=%zu, ref_count=%d",
entry, q->count, q->total_bytes, entry->ref_count);
// Инвалидировать позиции
q->version++;
// Если коллбэки разрешены - вызвать коллбэк
// Это запускает автоматическую обработку очереди
if (!q->callback_suspended && q->callback) {
@ -232,7 +295,7 @@ int queue_entry_put(struct ll_queue* q, struct ll_entry* entry) {
// Приостановить коллбэки во время выполнения коллбэка, чтобы предотвратить рекурсию
q->callback_suspended = 1;
q->callback(q, entry, q->callback_arg);
q->callback(q, q->head, q->callback_arg);
// Не восстанавливать здесь - восстановление происходит через queue_resume_callback
}
@ -258,22 +321,30 @@ int queue_entry_put_first(struct ll_queue* q, struct ll_entry* entry) {
// Увеличить счетчик ссылок при добавлении в очередь
entry->ref_count++;
// Добавить в голову (LIFO, высокий приоритет)
// Добавить в голову (LIFO, высокий приоритет) - двусвязный список
entry->next = q->head;
q->head = entry;
if (!q->tail) {
q->tail = entry;
entry->prev = NULL;
if (q->head) {
q->head->prev = entry;
} else {
q->tail = entry; // Очередь была пустой
}
q->head = entry;
q->count++;
q->total_bytes += entry->size;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: added entry %p, new count=%d, total_bytes=%zu, ref_count=%d",
entry, q->count, q->total_bytes, entry->ref_count);
// Инвалидировать позиции
q->version++;
// Если коллбэки разрешены - вызвать коллбэк
if (!q->callback_suspended && q->callback) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: calling callback for entry %p", entry);
q->callback(q, entry, q->callback_arg);
q->callback_suspended = 1;
q->callback(q, q->head, q->callback_arg);
}
// Проверить ожидающие коллбэки
@ -289,9 +360,12 @@ struct ll_entry* queue_entry_get(struct ll_queue* q) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: retrieving entry %p, size=%zu, ref_count=%d",
entry, entry->size, entry->ref_count);
// Удалить из двусвязного списка
q->head = entry->next;
if (!q->head) {
q->tail = NULL;
if (q->head) {
q->head->prev = NULL;
} else {
q->tail = NULL; // Очередь стала пустой
}
q->count--;
q->total_bytes -= entry->size;
@ -299,13 +373,17 @@ struct ll_entry* queue_entry_get(struct ll_queue* q) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: removed entry %p, new count=%d, total_bytes=%zu",
entry, q->count, q->total_bytes);
entry->next = NULL; // Отсоединить от очереди
entry->next = NULL;
entry->prev = NULL; // Полностью отсоединить от очереди
// Уменьшить счетчик ссылок при извлечении из очереди
// entry->ref_count был увеличен при добавлении в очередь
// теперь уменьшаем, но не освобождаем, так как вызывающий код должен это сделать
entry->ref_count--;
// Инвалидировать позиции
q->version++;
// При извлечении элемента приостанавливаем коллбэки
// Это предотвращает рекурсию если во время обработки добавляются новые элементы
q->callback_suspended = 1;
@ -350,7 +428,11 @@ struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, s
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: condition already met, count=%d<=%d, bytes=%zu<=%zu, calling callback",
q->count, max_packets, q->total_bytes, max_bytes);
callback(q, arg);
if (q->pool) {
memory_pool_free(q->pool, waiter);
} else {
free(waiter);
}
return NULL;
}
@ -398,3 +480,368 @@ void queue_get_pool_stats(struct ll_queue* q, size_t* waiter_allocations, size_t
memory_pool_get_stats(q->pool, waiter_allocations, waiter_reuse);
}
// ==================== Управление позициями ====================
struct ll_queue_pos* queue_pos_new(struct ll_queue* queue) {
if (!queue) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: queue is NULL");
return NULL;
}
struct ll_queue_pos* pos = malloc(sizeof(struct ll_queue_pos));
if (!pos) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: failed to allocate position");
return NULL;
}
pos->queue = queue;
pos->current = NULL;
pos->prev = NULL;
pos->index = -1; // Перед head
pos->valid = 1;
pos->version = queue->version;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: created position %p for queue %p", pos, queue);
return pos;
}
void queue_pos_free(struct ll_queue_pos* pos) {
if (!pos) return;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_free: freeing position %p", pos);
free(pos);
}
void queue_pos_reset(struct ll_queue_pos* pos) {
if (!pos) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_reset: pos is NULL");
return;
}
// Обновить версию позиции при необходимости (вместо инвалидации)
if (pos->queue->version != pos->version) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_reset: updating position version from %lu to %lu",
(unsigned long)pos->version, (unsigned long)pos->queue->version);
pos->version = pos->queue->version;
}
pos->current = NULL;
pos->prev = NULL;
pos->index = -1;
pos->valid = 1;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_reset: position reset");
}
int queue_pos_next(struct ll_queue_pos* pos) {
if (!pos) return 0;
// Убедиться, что позиция валидна (автоматическое обновление при необходимости)
if (!queue_pos_ensure_valid(pos)) {
return 0;
}
if (!pos->current) {
// Перед head - переходим к head
if (!pos->queue->head) {
// Очередь пуста - некуда переходить
return 0;
}
pos->current = pos->queue->head;
pos->prev = NULL;
pos->index = 0;
} else if (pos->current->next) {
// Есть следующий элемент
pos->prev = pos->current;
pos->current = pos->current->next;
pos->index++;
} else {
// Достигнут конец очереди
return 0;
}
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_next: moved to index %d, entry %p",
pos->index, pos->current);
return 1;
}
int queue_pos_prev(struct ll_queue_pos* pos) {
if (!pos) return 0;
// Убедиться, что позиция валидна (автоматическое обновление при необходимости)
if (!queue_pos_ensure_valid(pos)) {
return 0;
}
if (!pos->current) {
// Уже перед head - дальше некуда
return 0;
} else if (pos->current->prev) {
// Есть предыдущий элемент
pos->current = pos->current->prev;
pos->prev = pos->current->prev;
pos->index--;
} else {
// Достигнуто начало очереди
pos->current = NULL;
pos->prev = NULL;
pos->index = -1;
}
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_prev: moved to index %d, entry %p",
pos->index, pos->current);
return 1;
}
int queue_pos_seek(struct ll_queue_pos* pos, int target_index) {
if (!pos) return 0;
// Убедиться, что позиция валидна (автоматическое обновление при необходимости)
if (!queue_pos_ensure_valid(pos)) {
return 0;
}
if (target_index < -1 || target_index >= pos->queue->count) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: invalid target index %d (count=%d)",
target_index, pos->queue->count);
return 0;
}
// Простая реализация: начинаем с начала и идем вперед
// Можно оптимизировать позже, если потребуется
queue_pos_reset(pos);
for (int i = 0; i <= target_index; i++) {
if (!queue_pos_next(pos)) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: failed to reach index %d", target_index);
return 0;
}
}
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: seeked to index %d, entry %p",
pos->index, pos->current);
return 1;
}
int queue_pos_insert(struct ll_queue_pos* pos, struct ll_entry* entry, int mode) {
if (!pos || !pos->valid || !entry) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: invalid parameters");
return -1;
}
if (pos->queue->version != pos->version) {
pos->valid = 0;
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: position invalidated due to queue version change");
return -1;
}
struct ll_queue* q = pos->queue;
// Проверить лимит размера
if (q->size_limit >= 0 && q->count >= q->size_limit) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: size limit exceeded, freeing entry %p", entry);
queue_entry_free(entry);
return -1;
}
entry->ref_count++;
if (mode == 0) {
// Вставить перед current
if (!pos->current) {
// Вставить в head
entry->next = q->head;
entry->prev = NULL;
if (q->head) {
q->head->prev = entry;
} else {
q->tail = entry; // Очередь была пустой
}
q->head = entry;
} else {
// Вставить перед current
entry->next = pos->current;
entry->prev = pos->current->prev;
if (pos->current->prev) {
pos->current->prev->next = entry;
} else {
q->head = entry; // Вставляем в начало
}
pos->current->prev = entry;
}
} else {
// Вставить после current
if (!pos->current) {
// Вставить в head (current = NULL, значит перед head)
entry->next = q->head;
entry->prev = NULL;
if (q->head) {
q->head->prev = entry;
} else {
q->tail = entry; // Очередь была пустой
}
q->head = entry;
} else {
// Вставить после current
entry->next = pos->current->next;
entry->prev = pos->current;
if (pos->current->next) {
pos->current->next->prev = entry;
} else {
q->tail = entry; // Вставляем в конец
}
pos->current->next = entry;
}
}
q->count++;
q->total_bytes += entry->size;
// Обновить версию очереди и позиции (вместо полной инвалидации)
q->version++;
// Обновить текущую позицию, если она активна
if (pos->current) {
pos->version = q->version;
pos->valid = 1; // Сделать позицию снова валидной
// Корректировка индекса в зависимости от режима вставки
if (mode == 0) {
// Вставка перед текущим - индекс остается тем же
// pos->index не меняется
} else {
// Вставка после текущего - текущий элемент смещается
pos->index++;
}
}
// Позиция остается валидной - убрали строку pos->valid = 0;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: inserted entry %p, mode=%d, count=%d",
entry, mode, q->count);
// Проверить ожидающие коллбэки
check_waiters(q);
return 0;
}
struct ll_entry* queue_pos_remove(struct ll_queue_pos* pos) {
if (!pos || !pos->valid || !pos->current) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_remove: invalid position or no current element");
return NULL;
}
// Убедиться, что позиция валидна (автоматическое обновление при необходимости)
if (!queue_pos_ensure_valid(pos)) {
return NULL;
}
struct ll_queue* q = pos->queue;
struct ll_entry* entry = pos->current;
// Удалить из двусвязного списка
if (entry->prev) {
entry->prev->next = entry->next;
} else {
q->head = entry->next; // Удаляем head
}
if (entry->next) {
entry->next->prev = entry->prev;
} else {
q->tail = entry->prev; // Удаляем tail
}
q->count--;
q->total_bytes -= entry->size;
// Обновить позицию: перейти к следующему элементу
pos->current = entry->next;
pos->prev = entry->prev;
if (pos->current) {
// Остались элементы после удаленного
pos->index = pos->index; // Индекс остается тем же
} else {
// Удалили последний элемент
pos->index = q->count; // Указывает за конец
if (pos->index == 0) {
pos->current = NULL;
pos->prev = NULL;
pos->index = -1;
}
}
entry->next = NULL;
entry->prev = NULL;
entry->ref_count--; // Уменьшить счетчик ссылок
// Обновить версию очереди и позицию (вместо полной инвалидации)
q->version++;
if (pos->current) {
pos->version = q->version;
pos->valid = 1; // Сделать позицию снова валидной
// pos->index уже обновлен выше
}
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_remove: removed entry %p, new count=%d",
entry, q->count);
// Проверить ожидающие коллбэки
check_waiters(q);
return entry;
}
int queue_entry_remove(struct ll_queue* q, struct ll_entry* entry) {
if (!q || !entry) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: invalid parameters");
return -1;
}
// Проверить, что элемент действительно в этой очереди
struct ll_entry* curr = q->head;
while (curr) {
if (curr == entry) {
break;
}
curr = curr->next;
}
if (!curr) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: entry %p not found in queue", entry);
return -1;
}
// Удалить из двусвязного списка
if (entry->prev) {
entry->prev->next = entry->next;
} else {
q->head = entry->next; // Удаляем head
}
if (entry->next) {
entry->next->prev = entry->prev;
} else {
q->tail = entry->prev; // Удаляем tail
}
q->count--;
q->total_bytes -= entry->size;
entry->next = NULL;
entry->prev = NULL;
entry->ref_count--;
// Инвалидировать позиции
q->version++;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: removed entry %p, new count=%d",
entry, q->count);
// Проверить ожидающие коллбэки
check_waiters(q);
return 0;
}

829
lib/ll_queue.c2

@ -0,0 +1,829 @@
// ll_queue.c
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include "ll_queue.h"
#include "u_async.h"
#include "debug_config.h"
// Предварительное объявление для отложенного возобновления
static void queue_resume_timeout_cb(void* arg);
// Проверить и запустить ожидающие коллбэки
static void check_waiters(struct ll_queue* q) {
if (!q || !q->waiters) return;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: checking waiters, count=%d, bytes=%zu",
q->count, q->total_bytes);
// Сначала собрать список коллбэков для вызова, чтобы избежать проблем с модификацией списка во время итерации
struct queue_waiter* to_call = NULL;
struct queue_waiter* to_call_tail = NULL;
struct queue_waiter** pprev = &q->waiters;
struct queue_waiter* waiter = q->waiters;
while (waiter) {
struct queue_waiter* next = waiter->next;
// Проверить условие: не больше max_packets и не больше max_bytes
// max_bytes = 0 означает "не проверять байты"
if (q->count <= waiter->max_packets && (waiter->max_bytes == 0 || q->total_bytes <= waiter->max_bytes)) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: condition met, calling callback, count=%d<=%d, bytes=%zu<=%zu (max_bytes_check=%s)",
q->count, waiter->max_packets, q->total_bytes, waiter->max_bytes,
waiter->max_bytes == 0 ? "disabled" : "enabled");
// Удалить waiter из списка
*pprev = next;
// Добавить в список для вызова
waiter->next = NULL;
if (to_call_tail) {
to_call_tail->next = waiter;
} else {
to_call = waiter;
}
to_call_tail = waiter;
} else {
// Условие не выполнено - оставить в списке
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: condition NOT met, count=%d>%d or bytes=%zu>%zu (max_bytes_check=%s)",
q->count, waiter->max_packets, q->total_bytes, waiter->max_bytes,
waiter->max_bytes == 0 ? "disabled" : "enabled");
pprev = &waiter->next;
}
waiter = next;
}
// Теперь вызвать коллбэки
waiter = to_call;
while (waiter) {
struct queue_waiter* next = waiter->next;
waiter->callback(q, waiter->callback_arg);
if (q->pool) {
memory_pool_free(q->pool, waiter);
} else {
free(waiter);
}
waiter = next;
}
}
// ==================== Управление очередью ====================
struct ll_queue* queue_new(struct UASYNC* ua, struct memory_pool* pool) {
if (!ua) return NULL;
struct ll_queue* q = calloc(1, sizeof(struct ll_queue));
if (!q) return NULL;
q->head = NULL;
q->tail = NULL;
q->count = 0;
q->total_bytes = 0;
q->size_limit = -1; // По умолчанию без ограничения
q->callback = NULL;
q->callback_arg = NULL;
q->callback_suspended = 0; // Коллбэки разрешены изначально
q->resume_timeout_id = NULL;
q->ua = ua;
q->waiters = NULL;
q->pool=pool;
q->version = 0;
return q;
}
void queue_free(struct ll_queue* q) {
if (!q) return;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: freeing queue %p, head=%p, tail=%p, count=%d",
q, q->head, q->tail, q->count);
// Освободить все элементы (уменьшить счетчик ссылок)
struct ll_entry* entry = q->head;
int entry_count = 0;
while (entry) {
struct ll_entry* next = entry->next;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: releasing entry %p (entry %d), ref_count=%d",
entry, entry_count++, entry->ref_count);
entry->ref_count--; // Дополнительно уменьшить за присутствие в очереди
queue_entry_free(entry); // Это уменьшит ref_count и освободит только если ref_count == 0
entry = next;
}
// Освободить все ожидающие коллбэки
struct queue_waiter* waiter = q->waiters;
int waiter_count = 0;
while (waiter) {
struct queue_waiter* next = waiter->next;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: freeing waiter %p (waiter %d)", waiter, waiter_count++);
if (q->pool) {
memory_pool_free(q->pool, waiter);
} else {
free(waiter);
}
waiter = next;
}
// Отменить отложенное возобновление если запланировано
if (q->resume_timeout_id) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: cancelling resume timeout %p", q->resume_timeout_id);
uasync_cancel_timeout(q->ua, q->resume_timeout_id);
}
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: freeing queue structure %p", q);
free(q);
}
// ==================== Конфигурация очереди ====================
void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg) {
if (!q) return;
q->callback = cbk_fn;
q->callback_arg = arg;
}
static void queue_resume_timeout_cb(void* arg) {
struct ll_queue* q = (struct ll_queue*)arg;
if (!q || !q->callback) return;
// Очистить ID таймаута (таймаут сработал)
q->resume_timeout_id = NULL;
// Разрешить коллбэки
q->callback_suspended = 0;
// Если в очереди есть элементы, вызвать коллбэк с первым элементом
// Обработчик должен извлечь этот элемент вызовом queue_entry_get()
if (q->head) {
q->callback(q, q->head, q->callback_arg);
}
}
void queue_resume_callback(struct ll_queue* q) {
if (!q || !q->callback) return;
// Если уже есть отложенное возобновление, ничего не делать
if (q->resume_timeout_id) {
return;
}
// Запланировать отложенное возобновление через uasync
q->resume_timeout_id = uasync_set_timeout(q->ua, 0, q, queue_resume_timeout_cb);
}
void queue_set_size_limit(struct ll_queue* q, int lim) {
if (!q) return;
q->size_limit = lim;
}
// ==================== Управление элементами ====================
struct ll_entry* queue_entry_new(size_t data_size) {
// Выделить память под структуру + область данных
struct ll_entry* entry = malloc(sizeof(struct ll_entry) + data_size);
if (!entry) return NULL;
entry->next = NULL;
entry->prev = NULL; // Инициализировать prev для двусвязного списка
entry->size = data_size;
entry->ref_count = 1; // Начальный счетчик ссылок
// Область данных оставить неинициализированной для производительности
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_new: created entry %p, size=%zu, ref_count=%d",
entry, data_size, entry->ref_count);
return entry;
}
void queue_entry_free(struct ll_entry* entry) {
if (!entry) return;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: entry=%p, ref_count=%d",
entry, entry->ref_count);
if (entry->ref_count <= 0) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: entry %p has invalid ref_count=%d",
entry, entry->ref_count);
return; // Предотвратить double-free
}
entry->ref_count--;
if (entry->ref_count == 0) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: actually freeing entry %p", entry);
free(entry);
} else {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: decremented ref_count to %d for entry %p",
entry->ref_count, entry);
}
}
// ==================== Операции с очередью ====================
int queue_entry_put(struct ll_queue* q, struct ll_entry* entry) {
if (!q || !entry) return -1;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: entry=%p, size=%zu, count=%d",
entry, entry->size, q->count);
// Проверить лимит размера
if (q->size_limit >= 0 && q->count >= q->size_limit) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: size limit exceeded, freeing entry %p", entry);
queue_entry_free(entry);
return -1;
}
// Увеличить счетчик ссылок при добавлении в очередь
entry->ref_count++;
// Добавить в хвост (FIFO) - двусвязный список
entry->next = NULL;
entry->prev = q->tail;
if (q->tail) {
q->tail->next = entry;
} else {
q->head = entry; // Очередь была пустой
}
q->tail = entry;
q->count++;
q->total_bytes += entry->size;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: added entry %p, new count=%d, total_bytes=%zu, ref_count=%d",
entry, q->count, q->total_bytes, entry->ref_count);
// Инвалидировать позиции
q->version++;
// Если коллбэки разрешены - вызвать коллбэк
// Это запускает автоматическую обработку очереди
if (!q->callback_suspended && q->callback) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: calling callback for entry %p", entry);
// Приостановить коллбэки во время выполнения коллбэка, чтобы предотвратить рекурсию
q->callback_suspended = 1;
q->callback(q, q->head, q->callback_arg);
// Не восстанавливать здесь - восстановление происходит через queue_resume_callback
}
// Проверить ожидающие коллбэки
check_waiters(q);
return 0;
}
int queue_entry_put_first(struct ll_queue* q, struct ll_entry* entry) {
if (!q || !entry) return -1;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: entry=%p, size=%zu, count=%d",
entry, entry->size, q->count);
// Проверить лимит размера
if (q->size_limit >= 0 && q->count >= q->size_limit) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: size limit exceeded, freeing entry %p", entry);
queue_entry_free(entry);
return -1;
}
// Увеличить счетчик ссылок при добавлении в очередь
entry->ref_count++;
// Добавить в голову (LIFO, высокий приоритет) - двусвязный список
entry->next = q->head;
entry->prev = NULL;
if (q->head) {
q->head->prev = entry;
} else {
q->tail = entry; // Очередь была пустой
}
q->head = entry;
q->count++;
q->total_bytes += entry->size;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: added entry %p, new count=%d, total_bytes=%zu, ref_count=%d",
entry, q->count, q->total_bytes, entry->ref_count);
// Инвалидировать позиции
q->version++;
// Если коллбэки разрешены - вызвать коллбэк
if (!q->callback_suspended && q->callback) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: calling callback for entry %p", entry);
q->callback_suspended = 1;
q->callback(q, q->head, q->callback_arg);
}
// Проверить ожидающие коллбэки
check_waiters(q);
return 0;
}
struct ll_entry* queue_entry_get(struct ll_queue* q) {
if (!q || !q->head) return NULL;
struct ll_entry* entry = q->head;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: retrieving entry %p, size=%zu, ref_count=%d",
entry, entry->size, entry->ref_count);
// Удалить из двусвязного списка
q->head = entry->next;
if (q->head) {
q->head->prev = NULL;
} else {
q->tail = NULL; // Очередь стала пустой
}
q->count--;
q->total_bytes -= entry->size;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: removed entry %p, new count=%d, total_bytes=%zu",
entry, q->count, q->total_bytes);
entry->next = NULL;
entry->prev = NULL; // Полностью отсоединить от очереди
// Уменьшить счетчик ссылок при извлечении из очереди
// entry->ref_count был увеличен при добавлении в очередь
// теперь уменьшаем, но не освобождаем, так как вызывающий код должен это сделать
entry->ref_count--;
// Инвалидировать позиции
q->version++;
// При извлечении элемента приостанавливаем коллбэки
// Это предотвращает рекурсию если во время обработки добавляются новые элементы
q->callback_suspended = 1;
// Проверить ожидающие коллбэки при извлечении элемента
// Это важно для waiters, которые ожидают уменьшения очереди
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: about to call check_waiters, count=%d", q->count);
check_waiters(q);
return entry;
}
int queue_entry_count(struct ll_queue* q) {
if (!q) return 0;
return q->count;
}
// ==================== Асинхронное ожидание ====================
struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes,
queue_threshold_callback_fn callback, void* arg) {
if (!q || !callback) return NULL;
// Создать новый waiter
struct queue_waiter* waiter;
if (q->pool) {
waiter = (struct queue_waiter*)memory_pool_alloc(q->pool);
} else {
waiter = malloc(sizeof(struct queue_waiter));
}
if (!waiter) return NULL;
waiter->max_packets = max_packets;
waiter->max_bytes = max_bytes;
waiter->callback = callback;
waiter->callback_arg = arg;
waiter->next = NULL;
// Проверить условие немедленно
if (q->count <= max_packets && (max_bytes == 0 || q->total_bytes <= max_bytes)) {
// Условие уже выполнено - вызвать коллбэк и освободить waiter
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: condition already met, count=%d<=%d, bytes=%zu<=%zu, calling callback",
q->count, max_packets, q->total_bytes, max_bytes);
callback(q, arg);
if (q->pool) {
memory_pool_free(q->pool, waiter);
} else {
free(waiter);
}
return NULL;
}
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: registering waiter, count=%d, bytes=%zu, max_packets=%d, max_bytes=%zu",
q->count, q->total_bytes, max_packets, max_bytes);
// Добавить в список ожидающих
waiter->next = q->waiters;
q->waiters = waiter;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: waiter registered successfully");
return waiter;
}
void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter) {
if (!q || !waiter) return;
// Найти и удалить waiter из списка
struct queue_waiter** pprev = &q->waiters;
struct queue_waiter* w = q->waiters;
while (w) {
if (w == waiter) {
*pprev = w->next;
if (q->pool) {
memory_pool_free(q->pool, w);
} else {
free(w);
}
return;
}
pprev = &w->next;
w = w->next;
}
}
// ==================== Статистика и метрики ====================
void queue_get_pool_stats(struct ll_queue* q, size_t* waiter_allocations, size_t* waiter_reuse) {
if (!q || !q->pool) {
if (waiter_allocations) *waiter_allocations = 0;
if (waiter_reuse) *waiter_reuse = 0;
return;
}
memory_pool_get_stats(q->pool, waiter_allocations, waiter_reuse);
}
// ==================== Управление позициями ====================
struct ll_queue_pos* queue_pos_new(struct ll_queue* queue) {
if (!queue) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: queue is NULL");
return NULL;
}
struct ll_queue_pos* pos = malloc(sizeof(struct ll_queue_pos));
if (!pos) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: failed to allocate position");
return NULL;
}
pos->queue = queue;
pos->current = NULL;
pos->prev = NULL;
pos->index = -1; // Перед head
pos->valid = 1;
pos->version = queue->version;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: created position %p for queue %p", pos, queue);
return pos;
}
void queue_pos_free(struct ll_queue_pos* pos) {
if (!pos) return;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_free: freeing position %p", pos);
free(pos);
}
void queue_pos_reset(struct ll_queue_pos* pos) {
if (!pos) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_reset: pos is NULL");
return;
}
// Обновить версию позиции при необходимости (вместо инвалидации)
if (pos->queue->version != pos->version) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_reset: updating position version from %lu to %lu",
(unsigned long)pos->version, (unsigned long)pos->queue->version);
pos->version = pos->queue->version;
}
pos->current = NULL;
pos->prev = NULL;
pos->index = -1;
pos->valid = 1;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_reset: position reset");
}
int queue_pos_next(struct ll_queue_pos* pos) {
if (!pos || !pos->valid) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_next: invalid position");
return 0;
}
if (pos->queue->version != pos->version) {
pos->valid = 0;
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_next: position invalidated due to queue version change");
return 0;
}
if (!pos->current) {
// Перед head - переходим к head
if (!pos->queue->head) {
// Очередь пуста - некуда переходить
return 0;
}
pos->current = pos->queue->head;
pos->prev = NULL;
pos->index = 0;
} else if (pos->current->next) {
// Есть следующий элемент
pos->prev = pos->current;
pos->current = pos->current->next;
pos->index++;
} else {
// Достигнут конец очереди
return 0;
}
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_next: moved to index %d, entry %p",
pos->index, pos->current);
return 1;
}
int queue_pos_prev(struct ll_queue_pos* pos) {
if (!pos || !pos->valid) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_prev: invalid position");
return 0;
}
if (pos->queue->version != pos->version) {
pos->valid = 0;
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_prev: position invalidated due to queue version change");
return 0;
}
if (!pos->current) {
// Уже перед head - дальше некуда
return 0;
} else if (pos->current->prev) {
// Есть предыдущий элемент
pos->current = pos->current->prev;
pos->prev = pos->current->prev;
pos->index--;
} else {
// Достигнуто начало очереди
pos->current = NULL;
pos->prev = NULL;
pos->index = -1;
}
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_prev: moved to index %d, entry %p",
pos->index, pos->current);
return 1;
}
int queue_pos_seek(struct ll_queue_pos* pos, int target_index) {
if (!pos || !pos->valid) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: invalid position");
return 0;
}
// Обновить версию позиции при необходимости (вместо инвалидации)
if (pos->queue->version != pos->version) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: updating position version from %lu to %lu",
(unsigned long)pos->version, (unsigned long)pos->queue->version);
pos->version = pos->queue->version;
// Сбросить позицию, так как структура очереди могла измениться
queue_pos_reset(pos);
}
if (target_index < -1 || target_index >= pos->queue->count) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: invalid target index %d (count=%d)",
target_index, pos->queue->count);
return 0;
}
// Простая реализация: начинаем с начала и идем вперед
// Можно оптимизировать позже, если потребуется
queue_pos_reset(pos);
for (int i = 0; i <= target_index; i++) {
if (!queue_pos_next(pos)) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: failed to reach index %d", target_index);
return 0;
}
}
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: seeked to index %d, entry %p",
pos->index, pos->current);
return 1;
}
int queue_pos_insert(struct ll_queue_pos* pos, struct ll_entry* entry, int mode) {
if (!pos || !pos->valid || !entry) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: invalid parameters");
return -1;
}
if (pos->queue->version != pos->version) {
pos->valid = 0;
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: position invalidated due to queue version change");
return -1;
}
struct ll_queue* q = pos->queue;
// Проверить лимит размера
if (q->size_limit >= 0 && q->count >= q->size_limit) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: size limit exceeded, freeing entry %p", entry);
queue_entry_free(entry);
return -1;
}
entry->ref_count++;
if (mode == 0) {
// Вставить перед current
if (!pos->current) {
// Вставить в head
entry->next = q->head;
entry->prev = NULL;
if (q->head) {
q->head->prev = entry;
} else {
q->tail = entry; // Очередь была пустой
}
q->head = entry;
} else {
// Вставить перед current
entry->next = pos->current;
entry->prev = pos->current->prev;
if (pos->current->prev) {
pos->current->prev->next = entry;
} else {
q->head = entry; // Вставляем в начало
}
pos->current->prev = entry;
}
} else {
// Вставить после current
if (!pos->current) {
// Вставить в head (current = NULL, значит перед head)
entry->next = q->head;
entry->prev = NULL;
if (q->head) {
q->head->prev = entry;
} else {
q->tail = entry; // Очередь была пустой
}
q->head = entry;
} else {
// Вставить после current
entry->next = pos->current->next;
entry->prev = pos->current;
if (pos->current->next) {
pos->current->next->prev = entry;
} else {
q->tail = entry; // Вставляем в конец
}
pos->current->next = entry;
}
}
q->count++;
q->total_bytes += entry->size;
// Обновить версию очереди и позиции (вместо полной инвалидации)
q->version++;
// Обновить текущую позицию, если она активна
if (pos->current) {
pos->version = q->version;
pos->valid = 1; // Сделать позицию снова валидной
// Корректировка индекса в зависимости от режима вставки
if (mode == 0) {
// Вставка перед текущим - индекс остается тем же
// pos->index не меняется
} else {
// Вставка после текущего - текущий элемент смещается
pos->index++;
}
}
pos->valid = 0; // Текущая позиция теперь invalid
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: inserted entry %p, mode=%d, count=%d",
entry, mode, q->count);
// Проверить ожидающие коллбэки
check_waiters(q);
return 0;
}
struct ll_entry* queue_pos_remove(struct ll_queue_pos* pos) {
if (!pos || !pos->valid || !pos->current) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_remove: invalid position or no current element");
return NULL;
}
if (pos->queue->version != pos->version) {
pos->valid = 0;
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_remove: position invalidated due to queue version change");
return NULL;
}
struct ll_queue* q = pos->queue;
struct ll_entry* entry = pos->current;
// Удалить из двусвязного списка
if (entry->prev) {
entry->prev->next = entry->next;
} else {
q->head = entry->next; // Удаляем head
}
if (entry->next) {
entry->next->prev = entry->prev;
} else {
q->tail = entry->prev; // Удаляем tail
}
q->count--;
q->total_bytes -= entry->size;
// Обновить позицию: перейти к следующему элементу
pos->current = entry->next;
pos->prev = entry->prev;
if (pos->current) {
// Остались элементы после удаленного
pos->index = pos->index; // Индекс остается тем же
} else {
// Удалили последний элемент
pos->index = q->count; // Указывает за конец
if (pos->index == 0) {
pos->current = NULL;
pos->prev = NULL;
pos->index = -1;
}
}
entry->next = NULL;
entry->prev = NULL;
entry->ref_count--; // Уменьшить счетчик ссылок
// Обновить версию очереди и позицию (вместо полной инвалидации)
q->version++;
if (pos->current) {
pos->version = q->version;
pos->valid = 1; // Сделать позицию снова валидной
// pos->index уже обновлен выше
}
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_remove: removed entry %p, new count=%d",
entry, q->count);
// Проверить ожидающие коллбэки
check_waiters(q);
return entry;
}
int queue_entry_remove(struct ll_queue* q, struct ll_entry* entry) {
if (!q || !entry) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: invalid parameters");
return -1;
}
// Проверить, что элемент действительно в этой очереди
struct ll_entry* curr = q->head;
while (curr) {
if (curr == entry) {
break;
}
curr = curr->next;
}
if (!curr) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: entry %p not found in queue", entry);
return -1;
}
// Удалить из двусвязного списка
if (entry->prev) {
entry->prev->next = entry->next;
} else {
q->head = entry->next; // Удаляем head
}
if (entry->next) {
entry->next->prev = entry->prev;
} else {
q->tail = entry->prev; // Удаляем tail
}
q->count--;
q->total_bytes -= entry->size;
entry->next = NULL;
entry->prev = NULL;
entry->ref_count--;
// Инвалидировать позиции
q->version++;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: removed entry %p, new count=%d",
entry, q->count);
// Проверить ожидающие коллбэки
check_waiters(q);
return 0;
}

847
lib/ll_queue.c3

@ -0,0 +1,847 @@
// ll_queue.c
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include "ll_queue.h"
#include "u_async.h"
#include "debug_config.h"
// Предварительное объявление для отложенного возобновления
static void queue_resume_timeout_cb(void* arg);
// Проверить и запустить ожидающие коллбэки
static void check_waiters(struct ll_queue* q) {
if (!q || !q->waiters) return;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: checking waiters, count=%d, bytes=%zu",
q->count, q->total_bytes);
// Сначала собрать список коллбэков для вызова, чтобы избежать проблем с модификацией списка во время итерации
struct queue_waiter* to_call = NULL;
struct queue_waiter* to_call_tail = NULL;
struct queue_waiter** pprev = &q->waiters;
struct queue_waiter* waiter = q->waiters;
while (waiter) {
struct queue_waiter* next = waiter->next;
// Проверить условие: не больше max_packets и не больше max_bytes
// max_bytes = 0 означает "не проверять байты"
if (q->count <= waiter->max_packets && (waiter->max_bytes == 0 || q->total_bytes <= waiter->max_bytes)) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: condition met, calling callback, count=%d<=%d, bytes=%zu<=%zu (max_bytes_check=%s)",
q->count, waiter->max_packets, q->total_bytes, waiter->max_bytes,
waiter->max_bytes == 0 ? "disabled" : "enabled");
// Удалить waiter из списка
*pprev = next;
// Добавить в список для вызова
waiter->next = NULL;
if (to_call_tail) {
to_call_tail->next = waiter;
} else {
to_call = waiter;
}
to_call_tail = waiter;
} else {
// Условие не выполнено - оставить в списке
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: condition NOT met, count=%d>%d or bytes=%zu>%zu (max_bytes_check=%s)",
q->count, waiter->max_packets, q->total_bytes, waiter->max_bytes,
waiter->max_bytes == 0 ? "disabled" : "enabled");
pprev = &waiter->next;
}
waiter = next;
}
// Теперь вызвать коллбэки
waiter = to_call;
while (waiter) {
struct queue_waiter* next = waiter->next;
waiter->callback(q, waiter->callback_arg);
if (q->pool) {
memory_pool_free(q->pool, waiter);
} else {
free(waiter);
}
waiter = next;
}
}
// ==================== Унифицированная система версий позиций ====================
// Унифицированная проверка версии позиции с автоматическим пересканированием
static int queue_pos_ensure_valid(struct ll_queue_pos* pos) {
if (!pos || !pos->queue) return 0;
// Если позиция уже валидна и версия совпадает, всё хорошо
if (pos->valid && pos->version == pos->queue->version) {
return 1;
}
// Несовпадение версии или позиция невалидна - нужно пересканировать
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "Position version %lu != queue version %lu, rescanning...",
(unsigned long)pos->version, (unsigned long)pos->queue->version);
// Сохранить целевой индекс (где мы хотим быть)
int target_index = pos->index;
// Обновить версию позиции до текущей
pos->version = pos->queue->version;
// Обработать краевой случай: индекс больше не существует из-за изменения размера очереди
if (target_index >= pos->queue->count) {
// Позиция выходит за пределы очереди - инвалидируем её
pos->valid = 0;
return 0;
}
// Простой подход: сбросить и перейти к тому же индексу
// Это найдёт элемент, который теперь находится в целевом индексе
queue_pos_reset(pos);
return queue_pos_seek(pos, target_index);
}
// ==================== Управление очередью ====================
struct ll_queue* queue_new(struct UASYNC* ua, struct memory_pool* pool) {
if (!ua) return NULL;
struct ll_queue* q = calloc(1, sizeof(struct ll_queue));
if (!q) return NULL;
q->head = NULL;
q->tail = NULL;
q->count = 0;
q->total_bytes = 0;
q->size_limit = -1; // По умолчанию без ограничения
q->callback = NULL;
q->callback_arg = NULL;
q->callback_suspended = 0; // Коллбэки разрешены изначально
q->resume_timeout_id = NULL;
q->ua = ua;
q->waiters = NULL;
q->pool=pool;
q->version = 0;
return q;
}
void queue_free(struct ll_queue* q) {
if (!q) return;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: freeing queue %p, head=%p, tail=%p, count=%d",
q, q->head, q->tail, q->count);
// Освободить все элементы (уменьшить счетчик ссылок)
struct ll_entry* entry = q->head;
int entry_count = 0;
while (entry) {
struct ll_entry* next = entry->next;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: releasing entry %p (entry %d), ref_count=%d",
entry, entry_count++, entry->ref_count);
entry->ref_count--; // Дополнительно уменьшить за присутствие в очереди
queue_entry_free(entry); // Это уменьшит ref_count и освободит только если ref_count == 0
entry = next;
}
// Освободить все ожидающие коллбэки
struct queue_waiter* waiter = q->waiters;
int waiter_count = 0;
while (waiter) {
struct queue_waiter* next = waiter->next;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: freeing waiter %p (waiter %d)", waiter, waiter_count++);
if (q->pool) {
memory_pool_free(q->pool, waiter);
} else {
free(waiter);
}
waiter = next;
}
// Отменить отложенное возобновление если запланировано
if (q->resume_timeout_id) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: cancelling resume timeout %p", q->resume_timeout_id);
uasync_cancel_timeout(q->ua, q->resume_timeout_id);
}
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: freeing queue structure %p", q);
free(q);
}
// ==================== Конфигурация очереди ====================
void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg) {
if (!q) return;
q->callback = cbk_fn;
q->callback_arg = arg;
}
static void queue_resume_timeout_cb(void* arg) {
struct ll_queue* q = (struct ll_queue*)arg;
if (!q || !q->callback) return;
// Очистить ID таймаута (таймаут сработал)
q->resume_timeout_id = NULL;
// Разрешить коллбэки
q->callback_suspended = 0;
// Если в очереди есть элементы, вызвать коллбэк с первым элементом
// Обработчик должен извлечь этот элемент вызовом queue_entry_get()
if (q->head) {
q->callback(q, q->head, q->callback_arg);
}
}
void queue_resume_callback(struct ll_queue* q) {
if (!q || !q->callback) return;
// Если уже есть отложенное возобновление, ничего не делать
if (q->resume_timeout_id) {
return;
}
// Запланировать отложенное возобновление через uasync
q->resume_timeout_id = uasync_set_timeout(q->ua, 0, q, queue_resume_timeout_cb);
}
void queue_set_size_limit(struct ll_queue* q, int lim) {
if (!q) return;
q->size_limit = lim;
}
// ==================== Управление элементами ====================
struct ll_entry* queue_entry_new(size_t data_size) {
// Выделить память под структуру + область данных
struct ll_entry* entry = malloc(sizeof(struct ll_entry) + data_size);
if (!entry) return NULL;
entry->next = NULL;
entry->prev = NULL; // Инициализировать prev для двусвязного списка
entry->size = data_size;
entry->ref_count = 1; // Начальный счетчик ссылок
// Область данных оставить неинициализированной для производительности
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_new: created entry %p, size=%zu, ref_count=%d",
entry, data_size, entry->ref_count);
return entry;
}
void queue_entry_free(struct ll_entry* entry) {
if (!entry) return;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: entry=%p, ref_count=%d",
entry, entry->ref_count);
if (entry->ref_count <= 0) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: entry %p has invalid ref_count=%d",
entry, entry->ref_count);
return; // Предотвратить double-free
}
entry->ref_count--;
if (entry->ref_count == 0) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: actually freeing entry %p", entry);
free(entry);
} else {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: decremented ref_count to %d for entry %p",
entry->ref_count, entry);
}
}
// ==================== Операции с очередью ====================
int queue_entry_put(struct ll_queue* q, struct ll_entry* entry) {
if (!q || !entry) return -1;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: entry=%p, size=%zu, count=%d",
entry, entry->size, q->count);
// Проверить лимит размера
if (q->size_limit >= 0 && q->count >= q->size_limit) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: size limit exceeded, freeing entry %p", entry);
queue_entry_free(entry);
return -1;
}
// Увеличить счетчик ссылок при добавлении в очередь
entry->ref_count++;
// Добавить в хвост (FIFO) - двусвязный список
entry->next = NULL;
entry->prev = q->tail;
if (q->tail) {
q->tail->next = entry;
} else {
q->head = entry; // Очередь была пустой
}
q->tail = entry;
q->count++;
q->total_bytes += entry->size;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: added entry %p, new count=%d, total_bytes=%zu, ref_count=%d",
entry, q->count, q->total_bytes, entry->ref_count);
// Инвалидировать позиции
q->version++;
// Если коллбэки разрешены - вызвать коллбэк
// Это запускает автоматическую обработку очереди
if (!q->callback_suspended && q->callback) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: calling callback for entry %p", entry);
// Приостановить коллбэки во время выполнения коллбэка, чтобы предотвратить рекурсию
q->callback_suspended = 1;
q->callback(q, q->head, q->callback_arg);
// Не восстанавливать здесь - восстановление происходит через queue_resume_callback
}
// Проверить ожидающие коллбэки
check_waiters(q);
return 0;
}
int queue_entry_put_first(struct ll_queue* q, struct ll_entry* entry) {
if (!q || !entry) return -1;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: entry=%p, size=%zu, count=%d",
entry, entry->size, q->count);
// Проверить лимит размера
if (q->size_limit >= 0 && q->count >= q->size_limit) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: size limit exceeded, freeing entry %p", entry);
queue_entry_free(entry);
return -1;
}
// Увеличить счетчик ссылок при добавлении в очередь
entry->ref_count++;
// Добавить в голову (LIFO, высокий приоритет) - двусвязный список
entry->next = q->head;
entry->prev = NULL;
if (q->head) {
q->head->prev = entry;
} else {
q->tail = entry; // Очередь была пустой
}
q->head = entry;
q->count++;
q->total_bytes += entry->size;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: added entry %p, new count=%d, total_bytes=%zu, ref_count=%d",
entry, q->count, q->total_bytes, entry->ref_count);
// Инвалидировать позиции
q->version++;
// Если коллбэки разрешены - вызвать коллбэк
if (!q->callback_suspended && q->callback) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: calling callback for entry %p", entry);
q->callback_suspended = 1;
q->callback(q, q->head, q->callback_arg);
}
// Проверить ожидающие коллбэки
check_waiters(q);
return 0;
}
struct ll_entry* queue_entry_get(struct ll_queue* q) {
if (!q || !q->head) return NULL;
struct ll_entry* entry = q->head;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: retrieving entry %p, size=%zu, ref_count=%d",
entry, entry->size, entry->ref_count);
// Удалить из двусвязного списка
q->head = entry->next;
if (q->head) {
q->head->prev = NULL;
} else {
q->tail = NULL; // Очередь стала пустой
}
q->count--;
q->total_bytes -= entry->size;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: removed entry %p, new count=%d, total_bytes=%zu",
entry, q->count, q->total_bytes);
entry->next = NULL;
entry->prev = NULL; // Полностью отсоединить от очереди
// Уменьшить счетчик ссылок при извлечении из очереди
// entry->ref_count был увеличен при добавлении в очередь
// теперь уменьшаем, но не освобождаем, так как вызывающий код должен это сделать
entry->ref_count--;
// Инвалидировать позиции
q->version++;
// При извлечении элемента приостанавливаем коллбэки
// Это предотвращает рекурсию если во время обработки добавляются новые элементы
q->callback_suspended = 1;
// Проверить ожидающие коллбэки при извлечении элемента
// Это важно для waiters, которые ожидают уменьшения очереди
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: about to call check_waiters, count=%d", q->count);
check_waiters(q);
return entry;
}
int queue_entry_count(struct ll_queue* q) {
if (!q) return 0;
return q->count;
}
// ==================== Асинхронное ожидание ====================
struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes,
queue_threshold_callback_fn callback, void* arg) {
if (!q || !callback) return NULL;
// Создать новый waiter
struct queue_waiter* waiter;
if (q->pool) {
waiter = (struct queue_waiter*)memory_pool_alloc(q->pool);
} else {
waiter = malloc(sizeof(struct queue_waiter));
}
if (!waiter) return NULL;
waiter->max_packets = max_packets;
waiter->max_bytes = max_bytes;
waiter->callback = callback;
waiter->callback_arg = arg;
waiter->next = NULL;
// Проверить условие немедленно
if (q->count <= max_packets && (max_bytes == 0 || q->total_bytes <= max_bytes)) {
// Условие уже выполнено - вызвать коллбэк и освободить waiter
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: condition already met, count=%d<=%d, bytes=%zu<=%zu, calling callback",
q->count, max_packets, q->total_bytes, max_bytes);
callback(q, arg);
if (q->pool) {
memory_pool_free(q->pool, waiter);
} else {
free(waiter);
}
return NULL;
}
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: registering waiter, count=%d, bytes=%zu, max_packets=%d, max_bytes=%zu",
q->count, q->total_bytes, max_packets, max_bytes);
// Добавить в список ожидающих
waiter->next = q->waiters;
q->waiters = waiter;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: waiter registered successfully");
return waiter;
}
void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter) {
if (!q || !waiter) return;
// Найти и удалить waiter из списка
struct queue_waiter** pprev = &q->waiters;
struct queue_waiter* w = q->waiters;
while (w) {
if (w == waiter) {
*pprev = w->next;
if (q->pool) {
memory_pool_free(q->pool, w);
} else {
free(w);
}
return;
}
pprev = &w->next;
w = w->next;
}
}
// ==================== Статистика и метрики ====================
void queue_get_pool_stats(struct ll_queue* q, size_t* waiter_allocations, size_t* waiter_reuse) {
if (!q || !q->pool) {
if (waiter_allocations) *waiter_allocations = 0;
if (waiter_reuse) *waiter_reuse = 0;
return;
}
memory_pool_get_stats(q->pool, waiter_allocations, waiter_reuse);
}
// ==================== Управление позициями ====================
struct ll_queue_pos* queue_pos_new(struct ll_queue* queue) {
if (!queue) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: queue is NULL");
return NULL;
}
struct ll_queue_pos* pos = malloc(sizeof(struct ll_queue_pos));
if (!pos) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: failed to allocate position");
return NULL;
}
pos->queue = queue;
pos->current = NULL;
pos->prev = NULL;
pos->index = -1; // Перед head
pos->valid = 1;
pos->version = queue->version;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: created position %p for queue %p", pos, queue);
return pos;
}
void queue_pos_free(struct ll_queue_pos* pos) {
if (!pos) return;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_free: freeing position %p", pos);
free(pos);
}
void queue_pos_reset(struct ll_queue_pos* pos) {
if (!pos) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_reset: pos is NULL");
return;
}
// Обновить версию позиции при необходимости (вместо инвалидации)
if (pos->queue->version != pos->version) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_reset: updating position version from %lu to %lu",
(unsigned long)pos->version, (unsigned long)pos->queue->version);
pos->version = pos->queue->version;
}
pos->current = NULL;
pos->prev = NULL;
pos->index = -1;
pos->valid = 1;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_reset: position reset");
}
int queue_pos_next(struct ll_queue_pos* pos) {
if (!pos) return 0;
// Убедиться, что позиция валидна (автоматическое обновление при необходимости)
if (!queue_pos_ensure_valid(pos)) {
return 0;
}
if (!pos->current) {
// Перед head - переходим к head
if (!pos->queue->head) {
// Очередь пуста - некуда переходить
return 0;
}
pos->current = pos->queue->head;
pos->prev = NULL;
pos->index = 0;
} else if (pos->current->next) {
// Есть следующий элемент
pos->prev = pos->current;
pos->current = pos->current->next;
pos->index++;
} else {
// Достигнут конец очереди
return 0;
}
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_next: moved to index %d, entry %p",
pos->index, pos->current);
return 1;
}
int queue_pos_prev(struct ll_queue_pos* pos) {
if (!pos) return 0;
// Убедиться, что позиция валидна (автоматическое обновление при необходимости)
if (!queue_pos_ensure_valid(pos)) {
return 0;
}
if (!pos->current) {
// Уже перед head - дальше некуда
return 0;
} else if (pos->current->prev) {
// Есть предыдущий элемент
pos->current = pos->current->prev;
pos->prev = pos->current->prev;
pos->index--;
} else {
// Достигнуто начало очереди
pos->current = NULL;
pos->prev = NULL;
pos->index = -1;
}
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_prev: moved to index %d, entry %p",
pos->index, pos->current);
return 1;
}
int queue_pos_seek(struct ll_queue_pos* pos, int target_index) {
if (!pos) return 0;
// Убедиться, что позиция валидна (автоматическое обновление при необходимости)
if (!queue_pos_ensure_valid(pos)) {
return 0;
}
if (target_index < -1 || target_index >= pos->queue->count) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: invalid target index %d (count=%d)",
target_index, pos->queue->count);
return 0;
}
// Простая реализация: начинаем с начала и идем вперед
// Можно оптимизировать позже, если потребуется
queue_pos_reset(pos);
for (int i = 0; i <= target_index; i++) {
if (!queue_pos_next(pos)) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: failed to reach index %d", target_index);
return 0;
}
}
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: seeked to index %d, entry %p",
pos->index, pos->current);
return 1;
}
int queue_pos_insert(struct ll_queue_pos* pos, struct ll_entry* entry, int mode) {
if (!pos || !pos->valid || !entry) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: invalid parameters");
return -1;
}
if (pos->queue->version != pos->version) {
pos->valid = 0;
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: position invalidated due to queue version change");
return -1;
}
struct ll_queue* q = pos->queue;
// Проверить лимит размера
if (q->size_limit >= 0 && q->count >= q->size_limit) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: size limit exceeded, freeing entry %p", entry);
queue_entry_free(entry);
return -1;
}
entry->ref_count++;
if (mode == 0) {
// Вставить перед current
if (!pos->current) {
// Вставить в head
entry->next = q->head;
entry->prev = NULL;
if (q->head) {
q->head->prev = entry;
} else {
q->tail = entry; // Очередь была пустой
}
q->head = entry;
} else {
// Вставить перед current
entry->next = pos->current;
entry->prev = pos->current->prev;
if (pos->current->prev) {
pos->current->prev->next = entry;
} else {
q->head = entry; // Вставляем в начало
}
pos->current->prev = entry;
}
} else {
// Вставить после current
if (!pos->current) {
// Вставить в head (current = NULL, значит перед head)
entry->next = q->head;
entry->prev = NULL;
if (q->head) {
q->head->prev = entry;
} else {
q->tail = entry; // Очередь была пустой
}
q->head = entry;
} else {
// Вставить после current
entry->next = pos->current->next;
entry->prev = pos->current;
if (pos->current->next) {
pos->current->next->prev = entry;
} else {
q->tail = entry; // Вставляем в конец
}
pos->current->next = entry;
}
}
q->count++;
q->total_bytes += entry->size;
// Обновить версию очереди и позиции (вместо полной инвалидации)
q->version++;
// Обновить текущую позицию, если она активна
if (pos->current) {
pos->version = q->version;
pos->valid = 1; // Сделать позицию снова валидной
// Корректировка индекса в зависимости от режима вставки
if (mode == 0) {
// Вставка перед текущим - индекс остается тем же
// pos->index не меняется
} else {
// Вставка после текущего - текущий элемент смещается
pos->index++;
}
}
// Позиция остается валидной - убрали строку pos->valid = 0;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: inserted entry %p, mode=%d, count=%d",
entry, mode, q->count);
// Проверить ожидающие коллбэки
check_waiters(q);
return 0;
}
struct ll_entry* queue_pos_remove(struct ll_queue_pos* pos) {
if (!pos || !pos->valid || !pos->current) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_remove: invalid position or no current element");
return NULL;
}
// Убедиться, что позиция валидна (автоматическое обновление при необходимости)
if (!queue_pos_ensure_valid(pos)) {
return NULL;
}
struct ll_queue* q = pos->queue;
struct ll_entry* entry = pos->current;
// Удалить из двусвязного списка
if (entry->prev) {
entry->prev->next = entry->next;
} else {
q->head = entry->next; // Удаляем head
}
if (entry->next) {
entry->next->prev = entry->prev;
} else {
q->tail = entry->prev; // Удаляем tail
}
q->count--;
q->total_bytes -= entry->size;
// Обновить позицию: перейти к следующему элементу
pos->current = entry->next;
pos->prev = entry->prev;
if (pos->current) {
// Остались элементы после удаленного
pos->index = pos->index; // Индекс остается тем же
} else {
// Удалили последний элемент
pos->index = q->count; // Указывает за конец
if (pos->index == 0) {
pos->current = NULL;
pos->prev = NULL;
pos->index = -1;
}
}
entry->next = NULL;
entry->prev = NULL;
entry->ref_count--; // Уменьшить счетчик ссылок
// Обновить версию очереди и позицию (вместо полной инвалидации)
q->version++;
if (pos->current) {
pos->version = q->version;
pos->valid = 1; // Сделать позицию снова валидной
// pos->index уже обновлен выше
}
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_remove: removed entry %p, new count=%d",
entry, q->count);
// Проверить ожидающие коллбэки
check_waiters(q);
return entry;
}
int queue_entry_remove(struct ll_queue* q, struct ll_entry* entry) {
if (!q || !entry) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: invalid parameters");
return -1;
}
// Проверить, что элемент действительно в этой очереди
struct ll_entry* curr = q->head;
while (curr) {
if (curr == entry) {
break;
}
curr = curr->next;
}
if (!curr) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: entry %p not found in queue", entry);
return -1;
}
// Удалить из двусвязного списка
if (entry->prev) {
entry->prev->next = entry->next;
} else {
q->head = entry->next; // Удаляем head
}
if (entry->next) {
entry->next->prev = entry->prev;
} else {
q->tail = entry->prev; // Удаляем tail
}
q->count--;
q->total_bytes -= entry->size;
entry->next = NULL;
entry->prev = NULL;
entry->ref_count--;
// Инвалидировать позиции
q->version++;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: removed entry %p, new count=%d",
entry, q->count);
// Проверить ожидающие коллбэки
check_waiters(q);
return 0;
}

744
lib/ll_queue.c_

@ -0,0 +1,744 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include "ll_queue.h"
#include "u_async.h"
#include "debug_config.h"
// Предварительное объявление для отложенного возобновления
static void queue_resume_timeout_cb(void* arg);
// Вспомогательные функции для двусвязного списка
static void link_entry_after(struct ll_entry* existing, struct ll_entry* new_entry) {
if (!existing || !new_entry) return;
new_entry->next = existing->next;
new_entry->prev = existing;
if (existing->next) {
existing->next->prev = new_entry;
}
existing->next = new_entry;
}
static void unlink_entry(struct ll_entry* entry) {
if (!entry) return;
if (entry->prev) entry->prev->next = entry->next;
if (entry->next) entry->next->prev = entry->prev;
entry->next = NULL;
entry->prev = NULL;
}
// Проверить и запустить ожидающие коллбэки
static void check_waiters(struct ll_queue* q) {
if (!q || !q->waiters) return;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: checking waiters, count=%d, bytes=%zu",
q->count, q->total_bytes);
struct queue_waiter** pprev = &q->waiters;
struct queue_waiter* waiter = q->waiters;
while (waiter) {
struct queue_waiter* next = waiter->next;
// Проверить условие: не больше max_packets и не больше max_bytes
// max_bytes = 0 означает "не проверять байты"
if (q->count <= waiter->max_packets && (waiter->max_bytes == 0 || q->total_bytes <= waiter->max_bytes)) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: condition met, calling callback, count=%d<=%d, bytes=%zu<=%zu (max_bytes_check=%s)",
q->count, waiter->max_packets, q->total_bytes, waiter->max_bytes,
waiter->max_bytes == 0 ? "disabled" : "enabled");
waiter->callback(q, waiter->callback_arg);
// Удалить waiter из списка
*pprev = next;
if (q->pool) {
memory_pool_free(q->pool, waiter);
} else {
free(waiter);
}
// pprev уже указывает на правильный следующий элемент
} else {
// Условие не выполнено - оставить в списке
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: condition NOT met, count=%d>%d or bytes=%zu>%zu (max_bytes_check=%s)",
q->count, waiter->max_packets, q->total_bytes, waiter->max_bytes,
waiter->max_bytes == 0 ? "disabled" : "enabled");
pprev = &waiter->next;
}
waiter = next;
}
}
// ==================== Управление очередью ====================
struct ll_queue* queue_new(struct UASYNC* ua, struct memory_pool* pool) {
struct ll_queue* q = calloc(1, sizeof(struct ll_queue));
if (!q) return NULL;
q->head = NULL;
q->tail = NULL;
q->count = 0;
q->total_bytes = 0;
q->size_limit = -1; // По умолчанию без ограничения
q->callback = NULL;
q->callback_arg = NULL;
q->callback_suspended = 0; // Коллбэки разрешены изначально
q->resume_timeout_id = NULL;
q->ua = ua;
q->waiters = NULL;
q->pool=pool;
return q;
}
void queue_free(struct ll_queue* q) {
if (!q) return;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: freeing queue %p, head=%p, tail=%p, count=%d",
q, q->head, q->tail, q->count);
// Освободить все элементы (уменьшить счетчик ссылок)
struct ll_entry* entry = q->head;
int entry_count = 0;
while (entry) {
struct ll_entry* next = entry->next;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: releasing entry %p (entry %d), ref_count=%d",
entry, entry_count++, entry->ref_count);
queue_entry_free(entry); // Это уменьшит ref_count и освободит только если ref_count == 0
entry = next;
}
// Освободить все ожидающие коллбэки
struct queue_waiter* waiter = q->waiters;
int waiter_count = 0;
while (waiter) {
struct queue_waiter* next = waiter->next;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: freeing waiter %p (waiter %d)", waiter, waiter_count++);
if (q->pool) {
memory_pool_free(q->pool, waiter);
} else {
free(waiter);
}
waiter = next;
}
// Отменить отложенное возобновление если запланировано
if (q->resume_timeout_id) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: cancelling resume timeout %p", q->resume_timeout_id);
uasync_cancel_timeout(q->ua, q->resume_timeout_id);
}
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: freeing queue structure %p", q);
free(q);
}
// ==================== Конфигурация очереди ====================
void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg) {
if (!q) return;
q->callback = cbk_fn;
q->callback_arg = arg;
}
static void queue_resume_timeout_cb(void* arg) {
struct ll_queue* q = (struct ll_queue*)arg;
if (!q || !q->callback) return;
// Очистить ID таймаута (таймаут сработал)
q->resume_timeout_id = NULL;
// Разрешить коллбэки
q->callback_suspended = 0;
// Если в очереди есть элементы, вызвать коллбэк с первым элементом
// Обработчик должен извлечь этот элемент вызовом queue_entry_get()
if (q->head) {
q->callback(q, q->head, q->callback_arg);
}
}
void queue_resume_callback(struct ll_queue* q) {
if (!q || !q->callback) return;
// Если уже есть отложенное возобновление, ничего не делать
if (q->resume_timeout_id) {
return;
}
// Запланировать отложенное возобновление через uasync
q->resume_timeout_id = uasync_set_timeout(q->ua, 0, q, queue_resume_timeout_cb);
}
void queue_set_size_limit(struct ll_queue* q, int lim) {
if (!q) return;
q->size_limit = lim;
}
// ==================== Управление элементами ====================
struct ll_entry* queue_entry_new(size_t data_size) {
// Выделить память под структуру + область данных
struct ll_entry* entry = malloc(sizeof(struct ll_entry) + data_size);
if (!entry) return NULL;
entry->next = NULL;
entry->prev = NULL; // Инициализировать prev для двусвязного списка
entry->size = data_size;
entry->ref_count = 1; // Начальный счетчик ссылок
// Область данных оставить неинициализированной для производительности
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_new: created entry %p, size=%zu, ref_count=%d",
entry, data_size, entry->ref_count);
return entry;
}
void queue_entry_free(struct ll_entry* entry) {
if (!entry) return;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: entry=%p, ref_count=%d",
entry, entry->ref_count);
if (entry->ref_count <= 0) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: entry %p has invalid ref_count=%d",
entry, entry->ref_count);
return; // Предотвратить double-free
}
entry->ref_count--;
if (entry->ref_count == 0) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: actually freeing entry %p", entry);
free(entry);
} else {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: decremented ref_count to %d for entry %p",
entry->ref_count, entry);
}
}
// ==================== Операции с очередью ====================
int queue_entry_put(struct ll_queue* q, struct ll_entry* entry) {
if (!q || !entry) return -1;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: entry=%p, size=%zu, count=%d",
entry, entry->size, q->count);
// Проверить лимит размера
if (q->size_limit >= 0 && q->count >= q->size_limit) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: size limit exceeded, freeing entry %p", entry);
queue_entry_free(entry);
return -1;
}
// Увеличить счетчик ссылок при добавлении в очередь
entry->ref_count++;
// Добавить в хвост (FIFO) - двусвязный список
entry->next = NULL;
entry->prev = q->tail;
if (q->tail) {
q->tail->next = entry;
} else {
q->head = entry; // Очередь была пустой
}
q->tail = entry;
q->count++;
q->total_bytes += entry->size;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: added entry %p, new count=%d, total_bytes=%zu, ref_count=%d",
entry, q->count, q->total_bytes, entry->ref_count);
// Если коллбэки разрешены - вызвать коллбэк
// Это запускает автоматическую обработку очереди
if (!q->callback_suspended && q->callback) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: calling callback for entry %p", entry);
// Приостановить коллбэки во время выполнения коллбэка, чтобы предотвратить рекурсию
q->callback_suspended = 1;
q->callback(q, entry, q->callback_arg);
// Не восстанавливать здесь - восстановление происходит через queue_resume_callback
}
// Проверить ожидающие коллбэки
check_waiters(q);
return 0;
}
int queue_entry_put_first(struct ll_queue* q, struct ll_entry* entry) {
if (!q || !entry) return -1;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: entry=%p, size=%zu, count=%d",
entry, entry->size, q->count);
// Проверить лимит размера
if (q->size_limit >= 0 && q->count >= q->size_limit) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: size limit exceeded, freeing entry %p", entry);
queue_entry_free(entry);
return -1;
}
// Увеличить счетчик ссылок при добавлении в очередь
entry->ref_count++;
// Добавить в голову (LIFO, высокий приоритет) - двусвязный список
entry->next = q->head;
entry->prev = NULL;
if (q->head) {
q->head->prev = entry;
} else {
q->tail = entry; // Очередь была пустой
}
q->head = entry;
q->count++;
q->total_bytes += entry->size;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: added entry %p, new count=%d, total_bytes=%zu, ref_count=%d",
entry, q->count, q->total_bytes, entry->ref_count);
// Если коллбэки разрешены - вызвать коллбэк
if (!q->callback_suspended && q->callback) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: calling callback for entry %p", entry);
q->callback(q, entry, q->callback_arg);
}
// Проверить ожидающие коллбэки
check_waiters(q);
return 0;
}
struct ll_entry* queue_entry_get(struct ll_queue* q) {
if (!q || !q->head) return NULL;
struct ll_entry* entry = q->head;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: retrieving entry %p, size=%zu, ref_count=%d",
entry, entry->size, entry->ref_count);
// Удалить из двусвязного списка
q->head = entry->next;
if (q->head) {
q->head->prev = NULL;
} else {
q->tail = NULL; // Очередь стала пустой
}
q->count--;
q->total_bytes -= entry->size;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: removed entry %p, new count=%d, total_bytes=%zu",
entry, q->count, q->total_bytes);
entry->next = NULL;
entry->prev = NULL; // Полностью отсоединить от очереди
// Уменьшить счетчик ссылок при извлечении из очереди
// entry->ref_count был увеличен при добавлении в очередь
// теперь уменьшаем, но не освобождаем, так как вызывающий код должен это сделать
entry->ref_count--;
// При извлечении элемента приостанавливаем коллбэки
// Это предотвращает рекурсию если во время обработки добавляются новые элементы
q->callback_suspended = 1;
// Проверить ожидающие коллбэки при извлечении элемента
// Это важно для waiters, которые ожидают уменьшения очереди
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: about to call check_waiters, count=%d", q->count);
check_waiters(q);
return entry;
}
int queue_entry_count(struct ll_queue* q) {
if (!q) return 0;
return q->count;
}
// ==================== Асинхронное ожидание ====================
struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes,
queue_threshold_callback_fn callback, void* arg) {
if (!q || !callback) return NULL;
// Создать новый waiter
struct queue_waiter* waiter;
if (q->pool) {
waiter = (struct queue_waiter*)memory_pool_alloc(q->pool);
} else {
waiter = malloc(sizeof(struct queue_waiter));
}
if (!waiter) return NULL;
waiter->max_packets = max_packets;
waiter->max_bytes = max_bytes;
waiter->callback = callback;
waiter->callback_arg = arg;
waiter->next = NULL;
// Проверить условие немедленно
if (q->count <= max_packets && (max_bytes == 0 || q->total_bytes <= max_bytes)) {
// Условие уже выполнено - вызвать коллбэк и освободить waiter
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: condition already met, count=%d<=%d, bytes=%zu<=%zu, calling callback",
q->count, max_packets, q->total_bytes, max_bytes);
callback(q, arg);
free(waiter);
return NULL;
}
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: registering waiter, count=%d, bytes=%zu, max_packets=%d, max_bytes=%zu",
q->count, q->total_bytes, max_packets, max_bytes);
// Добавить в список ожидающих
waiter->next = q->waiters;
q->waiters = waiter;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: waiter registered successfully");
return waiter;
}
void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter) {
if (!q || !waiter) return;
// Найти и удалить waiter из списка
struct queue_waiter** pprev = &q->waiters;
struct queue_waiter* w = q->waiters;
while (w) {
if (w == waiter) {
*pprev = w->next;
if (q->pool) {
memory_pool_free(q->pool, w);
} else {
free(w);
}
return;
}
pprev = &w->next;
w = w->next;
}
}
// ==================== Статистика и метрики ====================
void queue_get_pool_stats(struct ll_queue* q, size_t* waiter_allocations, size_t* waiter_reuse) {
if (!q || !q->pool) {
if (waiter_allocations) *waiter_allocations = 0;
if (waiter_reuse) *waiter_reuse = 0;
return;
}
memory_pool_get_stats(q->pool, waiter_allocations, waiter_reuse);
}
// ==================== Управление позициями ====================
struct ll_queue_pos* queue_pos_new(struct ll_queue* queue) {
if (!queue) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: queue is NULL");
return NULL;
}
struct ll_queue_pos* pos = malloc(sizeof(struct ll_queue_pos));
if (!pos) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: failed to allocate position");
return NULL;
}
pos->queue = queue;
pos->current = NULL;
pos->prev = NULL;
pos->index = -1; // Перед head
pos->valid = 1;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: created position %p for queue %p", pos, queue);
return pos;
}
void queue_pos_free(struct ll_queue_pos* pos) {
if (!pos) return;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_free: freeing position %p", pos);
free(pos);
}
void queue_pos_reset(struct ll_queue_pos* pos) {
if (!pos) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_reset: pos is NULL");
return;
}
pos->current = NULL;
pos->prev = NULL;
pos->index = -1;
pos->valid = 1;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_reset: position reset");
}
int queue_pos_next(struct ll_queue_pos* pos) {
if (!pos || !pos->valid) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_next: invalid position");
return 0;
}
if (!pos->current) {
// Перед head - переходим к head
if (!pos->queue->head) {
// Очередь пуста - некуда переходить
return 0;
}
pos->current = pos->queue->head;
pos->prev = NULL;
pos->index = 0;
} else if (pos->current->next) {
// Есть следующий элемент
pos->prev = pos->current;
pos->current = pos->current->next;
pos->index++;
} else {
// Достигнут конец очереди
return 0;
}
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_next: moved to index %d, entry %p",
pos->index, pos->current);
return 1;
}
int queue_pos_prev(struct ll_queue_pos* pos) {
if (!pos || !pos->valid) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_prev: invalid position");
return 0;
}
if (!pos->current) {
// Уже перед head - дальше некуда
return 0;
} else if (pos->current->prev) {
// Есть предыдущий элемент
pos->current = pos->current->prev;
pos->prev = pos->current->prev;
pos->index--;
} else {
// Достигнуто начало очереди
pos->current = NULL;
pos->prev = NULL;
pos->index = -1;
}
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_prev: moved to index %d, entry %p",
pos->index, pos->current);
return 1;
}
int queue_pos_seek(struct ll_queue_pos* pos, int target_index) {
if (!pos || !pos->valid) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: invalid position");
return 0;
}
if (target_index < -1 || target_index >= pos->queue->count) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: invalid target index %d (count=%d)",
target_index, pos->queue->count);
return 0;
}
// Простая реализация: начинаем с начала и идем вперед
// Можно оптимизировать позже, если потребуется
queue_pos_reset(pos);
for (int i = 0; i <= target_index; i++) {
if (!queue_pos_next(pos)) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: failed to reach index %d", target_index);
return 0;
}
}
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: seeked to index %d, entry %p",
pos->index, pos->current);
return 1;
}
int queue_pos_insert(struct ll_queue_pos* pos, struct ll_entry* entry, int mode) {
if (!pos || !pos->valid || !entry) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: invalid parameters");
return -1;
}
struct ll_queue* q = pos->queue;
// Проверить лимит размера
if (q->size_limit >= 0 && q->count >= q->size_limit) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: size limit exceeded, freeing entry %p", entry);
queue_entry_free(entry);
return -1;
}
entry->ref_count++;
if (mode == 0) {
// Вставить перед current
if (!pos->current) {
// Вставить в head
entry->next = q->head;
entry->prev = NULL;
if (q->head) {
q->head->prev = entry;
} else {
q->tail = entry; // Очередь была пустой
}
q->head = entry;
} else {
// Вставить перед current
entry->next = pos->current;
entry->prev = pos->current->prev;
if (pos->current->prev) {
pos->current->prev->next = entry;
} else {
q->head = entry; // Вставляем в начало
}
pos->current->prev = entry;
}
} else {
// Вставить после current
if (!pos->current) {
// Вставить в head (current = NULL, значит перед head)
entry->next = q->head;
entry->prev = NULL;
if (q->head) {
q->head->prev = entry;
} else {
q->tail = entry; // Очередь была пустой
}
q->head = entry;
} else {
// Вставить после current
entry->next = pos->current->next;
entry->prev = pos->current;
if (pos->current->next) {
pos->current->next->prev = entry;
} else {
q->tail = entry; // Вставляем в конец
}
pos->current->next = entry;
}
}
q->count++;
q->total_bytes += entry->size;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: inserted entry %p, mode=%d, count=%d",
entry, mode, q->count);
// Проверить ожидающие коллбэки
check_waiters(q);
return 0;
}
struct ll_entry* queue_pos_remove(struct ll_queue_pos* pos) {
if (!pos || !pos->valid || !pos->current) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_remove: invalid position or no current element");
return NULL;
}
struct ll_queue* q = pos->queue;
struct ll_entry* entry = pos->current;
// Удалить из двусвязного списка
if (entry->prev) {
entry->prev->next = entry->next;
} else {
q->head = entry->next; // Удаляем head
}
if (entry->next) {
entry->next->prev = entry->prev;
} else {
q->tail = entry->prev; // Удаляем tail
}
q->count--;
q->total_bytes -= entry->size;
// Обновить позицию: перейти к следующему элементу
pos->current = entry->next;
pos->prev = entry->prev;
if (pos->current) {
// Остались элементы после удаленного
pos->index = pos->index; // Индекс остается тем же
} else {
// Удалили последний элемент
pos->index = q->count; // Указывает за конец
if (pos->index == 0) {
pos->current = NULL;
pos->prev = NULL;
pos->index = -1;
}
}
entry->next = NULL;
entry->prev = NULL;
entry->ref_count--; // Уменьшить счетчик ссылок
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_remove: removed entry %p, new count=%d",
entry, q->count);
// Проверить ожидающие коллбэки
check_waiters(q);
return entry;
}
int queue_entry_remove(struct ll_queue* q, struct ll_entry* entry) {
if (!q || !entry) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: invalid parameters");
return -1;
}
// Проверить, что элемент действительно в этой очереди
struct ll_entry* curr = q->head;
while (curr) {
if (curr == entry) {
break;
}
curr = curr->next;
}
if (!curr) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: entry %p not found in queue", entry);
return -1;
}
// Удалить из двусвязного списка
if (entry->prev) {
entry->prev->next = entry->next;
} else {
q->head = entry->next; // Удаляем head
}
if (entry->next) {
entry->next->prev = entry->prev;
} else {
q->tail = entry->prev; // Удаляем tail
}
q->count--;
q->total_bytes -= entry->size;
entry->next = NULL;
entry->prev = NULL;
entry->ref_count--;
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: removed entry %p, new count=%d",
entry, q->count);
// Проверить ожидающие коллбэки
check_waiters(q);
return 0;
}

78
lib/ll_queue.h

@ -1,7 +1,9 @@
// ll_queue.h
#ifndef LL_QUEUE_H
#define LL_QUEUE_H
#include <stddef.h> // для size_t
#include <stdint.h> // для uint64_t
#include "memory_pool.h" // для struct memory_pool
// Предварительные объявления
@ -9,6 +11,7 @@ struct ll_queue;
struct ll_entry;
struct queue_waiter;
struct memory_pool;
struct ll_queue_pos; // Управляющая структура позиции
// Тип коллбэка: вызывается при добавлении элемента в пустую очередь или для продолжения обработки
// Параметры: указатель на очередь, указатель на элемент (первый в очереди), пользовательский аргумент
@ -17,6 +20,7 @@ typedef void (*queue_callback_fn)(struct ll_queue* q, struct ll_entry* entry, vo
// Структура элемента - переменный размер, данные расположены сразу после структуры
struct ll_entry {
struct ll_entry* next; // Указатель на следующий элемент в очереди
struct ll_entry* prev; // Указатель на предыдущий элемент в очереди
size_t size; // Размер данных элемента (байт)
int ref_count; // Счетчик ссылок для предотвращения double-free
};
@ -24,7 +28,7 @@ struct ll_entry {
// Структура условия ожидания (waiter)
struct queue_waiter {
int max_packets; // Максимальное количество пакетов
size_t max_bytes; // Максимальное количество байт
size_t max_bytes; // Максимальное количество байт (0 = не проверять байты)
void (*callback)(struct ll_queue* q, void* arg); // Коллбэк для вызова
void* callback_arg; // Аргумент коллбэка
struct queue_waiter* next; // Следующий ожидающий в списке
@ -51,6 +55,8 @@ struct ll_queue {
// Пулы памяти для оптимизации аллокаций
struct memory_pool* pool; // Пул для структур struct queue_waiter
uint64_t version; // Версия для инвалидации позиций
};
// ==================== Управление очередью ====================
@ -117,18 +123,20 @@ int queue_entry_count(struct ll_queue* q);
// Получить указатель на область данных элемента
// Данные расположены сразу после структуры struct ll_entry
static inline void* ll_entry_data(struct ll_entry* entry) {
if (!entry) return NULL;
return (void*)(entry + 1);
}
// Получить размер данных элемента
static inline size_t ll_entry_size(struct ll_entry* entry) {
if (!entry) return 0;
return entry->size;
}
// ==================== Асинхронное ожидание ====================
// Зарегистрировать одноразовый коллбэк, который будет вызван когда очередь будет иметь
// не более max_packets пакетов и не более max_bytes байт.
// не более max_packets пакетов и не более max_bytes байт (если max_bytes != 0).
// Если условие уже выполнено, коллбэк вызывается немедленно.
// Можно зарегистрировать несколько ожиданий на одной очереди.
// Возвращает указатель на waiter для возможной отмены через queue_cancel_wait
@ -147,4 +155,70 @@ static inline size_t queue_total_bytes(struct ll_queue* q) {
// Получить статистику использования пулов памяти
void queue_get_pool_stats(struct ll_queue* q, size_t* waiter_allocations, size_t* waiter_reuse);
// ==================== Управление позициями ====================
// Структура управления позицией для навигации по очереди
struct ll_queue_pos {
struct ll_queue* queue; // Ссылка на очередь
struct ll_entry* current; // Текущий элемент (NULL = перед head)
struct ll_entry* prev; // Предыдущий элемент (для навигации)
int index; // Текущий индекс (-1 = перед head)
int valid; // Флаг валидности позиции
uint64_t version; // Версия очереди на момент создания/обновления позиции
};
// Создать новую позицию для очереди
struct ll_queue_pos* queue_pos_new(struct ll_queue* queue);
// Освободить позицию (не удаляет элементы очереди)
void queue_pos_free(struct ll_queue_pos* pos);
// Сбросить позицию в начальное состояние (перед head)
void queue_pos_reset(struct ll_queue_pos* pos);
// Перейти к следующему элементу
// Возвращает: 1 при успехе, 0 если достигнут конец
int queue_pos_next(struct ll_queue_pos* pos);
// Перейти к предыдущему элементу
// Возвращает: 1 при успехе, 0 если достигнуто начало
int queue_pos_prev(struct ll_queue_pos* pos);
// Перейти к конкретному индексу (0 = head)
// Возвращает: 1 при успехе, 0 если индекс неверен
int queue_pos_seek(struct ll_queue_pos* pos, int index);
// Получить текущий элемент без удаления
static inline struct ll_entry* queue_pos_current(struct ll_queue_pos* pos) {
if (!pos || !pos->valid) return NULL;
return pos->current;
}
// Получить текущий индекс
static inline int queue_pos_index(struct ll_queue_pos* pos) {
if (!pos || !pos->valid) return -1;
return pos->index;
}
// Обновить позицию после изменения очереди (сбросить версию)
static inline void queue_pos_refresh(struct ll_queue_pos* pos) {
if (!pos || !pos->queue) return;
pos->version = pos->queue->version;
pos->valid = 1;
}
// Вставить элемент относительно текущей позиции
// mode: 0 = перед current, 1 = после current
// Возвращает: 0 при успехе, -1 при ошибке
int queue_pos_insert(struct ll_queue_pos* pos, struct ll_entry* entry, int mode);
// Удалить элемент из текущей позиции и вернуть его
// После удаления current указывает на следующий элемент (или NULL)
// Возвращает: указатель на удаленный элемент или NULL
struct ll_entry* queue_pos_remove(struct ll_queue_pos* pos);
// Удалить конкретный элемент из очереди по указателю
// Возвращает: 0 при успехе, -1 если элемент не найден
int queue_entry_remove(struct ll_queue* q, struct ll_entry* entry);
#endif // LL_QUEUE_H

224
lib/ll_queue.h2

@ -0,0 +1,224 @@
// ll_queue.h
#ifndef LL_QUEUE_H
#define LL_QUEUE_H
#include <stddef.h> // для size_t
#include <stdint.h> // для uint64_t
#include "memory_pool.h" // для struct memory_pool
// Предварительные объявления
struct ll_queue;
struct ll_entry;
struct queue_waiter;
struct memory_pool;
struct ll_queue_pos; // Управляющая структура позиции
// Тип коллбэка: вызывается при добавлении элемента в пустую очередь или для продолжения обработки
// Параметры: указатель на очередь, указатель на элемент (первый в очереди), пользовательский аргумент
typedef void (*queue_callback_fn)(struct ll_queue* q, struct ll_entry* entry, void* arg);
// Структура элемента - переменный размер, данные расположены сразу после структуры
struct ll_entry {
struct ll_entry* next; // Указатель на следующий элемент в очереди
struct ll_entry* prev; // Указатель на предыдущий элемент в очереди
size_t size; // Размер данных элемента (байт)
int ref_count; // Счетчик ссылок для предотвращения double-free
};
// Структура условия ожидания (waiter)
struct queue_waiter {
int max_packets; // Максимальное количество пакетов
size_t max_bytes; // Максимальное количество байт (0 = не проверять байты)
void (*callback)(struct ll_queue* q, void* arg); // Коллбэк для вызова
void* callback_arg; // Аргумент коллбэка
struct queue_waiter* next; // Следующий ожидающий в списке
};
typedef void (*queue_threshold_callback_fn)(struct ll_queue* q, void* arg);
// Структура очереди
struct ll_queue {
struct ll_entry* head; // Первый элемент (извлекается отсюда)
struct ll_entry* tail; // Последний элемент (добавляется сюда)
int count; // Текущее количество элементов
size_t total_bytes; // Общий размер данных всех элементов (байт)
int size_limit; // Максимальное количество (-1 = без ограничения)
queue_callback_fn callback; // Функция коллбэка
void* callback_arg; // Пользовательский аргумент для коллбэка
int callback_suspended; // 1 если коллбэки приостановлены (во время обработки)
void* resume_timeout_id; // ID таймаута uasync для отложенного возобновления
struct UASYNC* ua; // Экземпляр uasync для таймеров
struct queue_waiter* waiters; // Список ожидающих коллбэков
// Пулы памяти для оптимизации аллокаций
struct memory_pool* pool; // Пул для структур struct queue_waiter
uint64_t version; // Версия для инвалидации позиций
};
// ==================== Управление очередью ====================
// Создать новую пустую очередь
// ua - экземпляр uasync для таймеров (обязательный параметр)
// pool - если не null то использовать этот пул памяти
// Возвращает: указатель на очередь или NULL при ошибке выделения памяти (see memory_pool.c/h)
struct ll_queue* queue_new(struct UASYNC* ua, struct memory_pool* pool);
// Освободить очередь и все её элементы
// Также отменяет отложенное возобновление если оно запланировано
void queue_free(struct ll_queue* q);
// ==================== Конфигурация очереди ====================
// Установить функцию и аргумент коллбэка для автозабора из очереди
// Коллбэк вызывается когда в очереди есть элемент и разрешен коллбэк
// обработчик должен обработать этот пакет (может использовать асинхронное ожидание). Когда будет готов к приёму следующего - должен вызвать resume_callback. обработка строго по одному пакету.
void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
// Возобновить коллбэки после обработки элемента переданного в коллбэке (тянуть дополнительные элементы из очереди не предусмотернные api нельзя).
// эта функция должна вызываться всегда после того как cbk_fn обработала пакет (можно с ожиданием через async), иначе очередь застрянет.
// Если в очереди остались элементы, запланирует вызов коллбэка через uasync_set_timeout(0)
// Это предотвращает накопление рекурсии в стеке вызовов
void queue_resume_callback(struct ll_queue* q);
// Установить максимальное количество элементов в очереди
// При превышении лимита новый элемент автоматически освобождается
void queue_set_size_limit(struct ll_queue* q, int lim);
// ==================== Управление элементами ====================
// Создать новый элемент с областью данных указанного размера
// Память выделяется одним блоком: [struct ll_entry][область данных data_size байт]
// Возвращает: указатель на элемент или NULL при ошибке выделения памяти
struct ll_entry* queue_entry_new(size_t data_size);
// Освободить элемент (не влияет на связи в очереди)
void queue_entry_free(struct ll_entry* entry);
// ==================== Операции с очередью ====================
// Добавить элемент в конец очереди (FIFO)
// Если очередь была пустой и коллбэки разрешены - вызывает коллбэк
// Возвращает: 0 при успехе, -1 если превышен лимит размера (элемент освобожден)
int queue_entry_put(struct ll_queue* q, struct ll_entry* entry);
// Добавить элемент в начало очереди (LIFO, высокий приоритет)
// Если очередь была пустой и коллбэки разрешены - вызывает коллбэк
// Возвращает: 0 при успехе, -1 если превышен лимит размера (элемент освобожден)
int queue_entry_put_first(struct ll_queue* q, struct ll_entry* entry);
// Извлечь элемент из начала очереди
// При извлечении приостанавливает коллбэки (callback_suspended = 1) чтобы предотвратить рекурсию
// Возвращает: указатель на элемент или NULL если очередь пуста
struct ll_entry* queue_entry_get(struct ll_queue* q);
// Получить текущее количество элементов в очереди
int queue_entry_count(struct ll_queue* q);
// ==================== Вспомогательные функции ====================
// Получить указатель на область данных элемента
// Данные расположены сразу после структуры struct ll_entry
static inline void* ll_entry_data(struct ll_entry* entry) {
if (!entry) return NULL;
return (void*)(entry + 1);
}
// Получить размер данных элемента
static inline size_t ll_entry_size(struct ll_entry* entry) {
if (!entry) return 0;
return entry->size;
}
// ==================== Асинхронное ожидание ====================
// Зарегистрировать одноразовый коллбэк, который будет вызван когда очередь будет иметь
// не более max_packets пакетов и не более max_bytes байт (если max_bytes != 0).
// Если условие уже выполнено, коллбэк вызывается немедленно.
// Можно зарегистрировать несколько ожиданий на одной очереди.
// Возвращает указатель на waiter для возможной отмены через queue_cancel_wait
struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes,
queue_threshold_callback_fn callback, void* arg);
// Отменить ожидание (удалить waiter из списка)
void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter);
// Получить общий размер данных в очереди (байт)
static inline size_t queue_total_bytes(struct ll_queue* q) {
if (!q) return 0;
return q->total_bytes;
}
// Получить статистику использования пулов памяти
void queue_get_pool_stats(struct ll_queue* q, size_t* waiter_allocations, size_t* waiter_reuse);
// ==================== Управление позициями ====================
// Структура управления позицией для навигации по очереди
struct ll_queue_pos {
struct ll_queue* queue; // Ссылка на очередь
struct ll_entry* current; // Текущий элемент (NULL = перед head)
struct ll_entry* prev; // Предыдущий элемент (для навигации)
int index; // Текущий индекс (-1 = перед head)
int valid; // Флаг валидности позиции
uint64_t version; // Версия очереди на момент создания/обновления позиции
};
// Создать новую позицию для очереди
struct ll_queue_pos* queue_pos_new(struct ll_queue* queue);
// Освободить позицию (не удаляет элементы очереди)
void queue_pos_free(struct ll_queue_pos* pos);
// Сбросить позицию в начальное состояние (перед head)
void queue_pos_reset(struct ll_queue_pos* pos);
// Перейти к следующему элементу
// Возвращает: 1 при успехе, 0 если достигнут конец
int queue_pos_next(struct ll_queue_pos* pos);
// Перейти к предыдущему элементу
// Возвращает: 1 при успехе, 0 если достигнуто начало
int queue_pos_prev(struct ll_queue_pos* pos);
// Перейти к конкретному индексу (0 = head)
// Возвращает: 1 при успехе, 0 если индекс неверен
int queue_pos_seek(struct ll_queue_pos* pos, int index);
// Получить текущий элемент без удаления
static inline struct ll_entry* queue_pos_current(struct ll_queue_pos* pos) {
if (!pos || !pos->valid) return NULL;
return pos->current;
}
// Получить текущий индекс
static inline int queue_pos_index(struct ll_queue_pos* pos) {
if (!pos || !pos->valid) return -1;
return pos->index;
}
// Обновить позицию после изменения очереди (сбросить версию)
static inline void queue_pos_refresh(struct ll_queue_pos* pos) {
if (!pos || !pos->queue) return;
pos->version = pos->queue->version;
pos->valid = 1;
}
// Вставить элемент относительно текущей позиции
// mode: 0 = перед current, 1 = после current
// Возвращает: 0 при успехе, -1 при ошибке
int queue_pos_insert(struct ll_queue_pos* pos, struct ll_entry* entry, int mode);
// Удалить элемент из текущей позиции и вернуть его
// После удаления current указывает на следующий элемент (или NULL)
// Возвращает: указатель на удаленный элемент или NULL
struct ll_entry* queue_pos_remove(struct ll_queue_pos* pos);
// Удалить конкретный элемент из очереди по указателю
// Возвращает: 0 при успехе, -1 если элемент не найден
int queue_entry_remove(struct ll_queue* q, struct ll_entry* entry);
#endif // LL_QUEUE_H

224
lib/ll_queue.h3

@ -0,0 +1,224 @@
// ll_queue.h
#ifndef LL_QUEUE_H
#define LL_QUEUE_H
#include <stddef.h> // для size_t
#include <stdint.h> // для uint64_t
#include "memory_pool.h" // для struct memory_pool
// Предварительные объявления
struct ll_queue;
struct ll_entry;
struct queue_waiter;
struct memory_pool;
struct ll_queue_pos; // Управляющая структура позиции
// Тип коллбэка: вызывается при добавлении элемента в пустую очередь или для продолжения обработки
// Параметры: указатель на очередь, указатель на элемент (первый в очереди), пользовательский аргумент
typedef void (*queue_callback_fn)(struct ll_queue* q, struct ll_entry* entry, void* arg);
// Структура элемента - переменный размер, данные расположены сразу после структуры
struct ll_entry {
struct ll_entry* next; // Указатель на следующий элемент в очереди
struct ll_entry* prev; // Указатель на предыдущий элемент в очереди
size_t size; // Размер данных элемента (байт)
int ref_count; // Счетчик ссылок для предотвращения double-free
};
// Структура условия ожидания (waiter)
struct queue_waiter {
int max_packets; // Максимальное количество пакетов
size_t max_bytes; // Максимальное количество байт (0 = не проверять байты)
void (*callback)(struct ll_queue* q, void* arg); // Коллбэк для вызова
void* callback_arg; // Аргумент коллбэка
struct queue_waiter* next; // Следующий ожидающий в списке
};
typedef void (*queue_threshold_callback_fn)(struct ll_queue* q, void* arg);
// Структура очереди
struct ll_queue {
struct ll_entry* head; // Первый элемент (извлекается отсюда)
struct ll_entry* tail; // Последний элемент (добавляется сюда)
int count; // Текущее количество элементов
size_t total_bytes; // Общий размер данных всех элементов (байт)
int size_limit; // Максимальное количество (-1 = без ограничения)
queue_callback_fn callback; // Функция коллбэка
void* callback_arg; // Пользовательский аргумент для коллбэка
int callback_suspended; // 1 если коллбэки приостановлены (во время обработки)
void* resume_timeout_id; // ID таймаута uasync для отложенного возобновления
struct UASYNC* ua; // Экземпляр uasync для таймеров
struct queue_waiter* waiters; // Список ожидающих коллбэков
// Пулы памяти для оптимизации аллокаций
struct memory_pool* pool; // Пул для структур struct queue_waiter
uint64_t version; // Версия для инвалидации позиций
};
// ==================== Управление очередью ====================
// Создать новую пустую очередь
// ua - экземпляр uasync для таймеров (обязательный параметр)
// pool - если не null то использовать этот пул памяти
// Возвращает: указатель на очередь или NULL при ошибке выделения памяти (see memory_pool.c/h)
struct ll_queue* queue_new(struct UASYNC* ua, struct memory_pool* pool);
// Освободить очередь и все её элементы
// Также отменяет отложенное возобновление если оно запланировано
void queue_free(struct ll_queue* q);
// ==================== Конфигурация очереди ====================
// Установить функцию и аргумент коллбэка для автозабора из очереди
// Коллбэк вызывается когда в очереди есть элемент и разрешен коллбэк
// обработчик должен обработать этот пакет (может использовать асинхронное ожидание). Когда будет готов к приёму следующего - должен вызвать resume_callback. обработка строго по одному пакету.
void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
// Возобновить коллбэки после обработки элемента переданного в коллбэке (тянуть дополнительные элементы из очереди не предусмотернные api нельзя).
// эта функция должна вызываться всегда после того как cbk_fn обработала пакет (можно с ожиданием через async), иначе очередь застрянет.
// Если в очереди остались элементы, запланирует вызов коллбэка через uasync_set_timeout(0)
// Это предотвращает накопление рекурсии в стеке вызовов
void queue_resume_callback(struct ll_queue* q);
// Установить максимальное количество элементов в очереди
// При превышении лимита новый элемент автоматически освобождается
void queue_set_size_limit(struct ll_queue* q, int lim);
// ==================== Управление элементами ====================
// Создать новый элемент с областью данных указанного размера
// Память выделяется одним блоком: [struct ll_entry][область данных data_size байт]
// Возвращает: указатель на элемент или NULL при ошибке выделения памяти
struct ll_entry* queue_entry_new(size_t data_size);
// Освободить элемент (не влияет на связи в очереди)
void queue_entry_free(struct ll_entry* entry);
// ==================== Операции с очередью ====================
// Добавить элемент в конец очереди (FIFO)
// Если очередь была пустой и коллбэки разрешены - вызывает коллбэк
// Возвращает: 0 при успехе, -1 если превышен лимит размера (элемент освобожден)
int queue_entry_put(struct ll_queue* q, struct ll_entry* entry);
// Добавить элемент в начало очереди (LIFO, высокий приоритет)
// Если очередь была пустой и коллбэки разрешены - вызывает коллбэк
// Возвращает: 0 при успехе, -1 если превышен лимит размера (элемент освобожден)
int queue_entry_put_first(struct ll_queue* q, struct ll_entry* entry);
// Извлечь элемент из начала очереди
// При извлечении приостанавливает коллбэки (callback_suspended = 1) чтобы предотвратить рекурсию
// Возвращает: указатель на элемент или NULL если очередь пуста
struct ll_entry* queue_entry_get(struct ll_queue* q);
// Получить текущее количество элементов в очереди
int queue_entry_count(struct ll_queue* q);
// ==================== Вспомогательные функции ====================
// Получить указатель на область данных элемента
// Данные расположены сразу после структуры struct ll_entry
static inline void* ll_entry_data(struct ll_entry* entry) {
if (!entry) return NULL;
return (void*)(entry + 1);
}
// Получить размер данных элемента
static inline size_t ll_entry_size(struct ll_entry* entry) {
if (!entry) return 0;
return entry->size;
}
// ==================== Асинхронное ожидание ====================
// Зарегистрировать одноразовый коллбэк, который будет вызван когда очередь будет иметь
// не более max_packets пакетов и не более max_bytes байт (если max_bytes != 0).
// Если условие уже выполнено, коллбэк вызывается немедленно.
// Можно зарегистрировать несколько ожиданий на одной очереди.
// Возвращает указатель на waiter для возможной отмены через queue_cancel_wait
struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes,
queue_threshold_callback_fn callback, void* arg);
// Отменить ожидание (удалить waiter из списка)
void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter);
// Получить общий размер данных в очереди (байт)
static inline size_t queue_total_bytes(struct ll_queue* q) {
if (!q) return 0;
return q->total_bytes;
}
// Получить статистику использования пулов памяти
void queue_get_pool_stats(struct ll_queue* q, size_t* waiter_allocations, size_t* waiter_reuse);
// ==================== Управление позициями ====================
// Структура управления позицией для навигации по очереди
struct ll_queue_pos {
struct ll_queue* queue; // Ссылка на очередь
struct ll_entry* current; // Текущий элемент (NULL = перед head)
struct ll_entry* prev; // Предыдущий элемент (для навигации)
int index; // Текущий индекс (-1 = перед head)
int valid; // Флаг валидности позиции
uint64_t version; // Версия очереди на момент создания/обновления позиции
};
// Создать новую позицию для очереди
struct ll_queue_pos* queue_pos_new(struct ll_queue* queue);
// Освободить позицию (не удаляет элементы очереди)
void queue_pos_free(struct ll_queue_pos* pos);
// Сбросить позицию в начальное состояние (перед head)
void queue_pos_reset(struct ll_queue_pos* pos);
// Перейти к следующему элементу
// Возвращает: 1 при успехе, 0 если достигнут конец
int queue_pos_next(struct ll_queue_pos* pos);
// Перейти к предыдущему элементу
// Возвращает: 1 при успехе, 0 если достигнуто начало
int queue_pos_prev(struct ll_queue_pos* pos);
// Перейти к конкретному индексу (0 = head)
// Возвращает: 1 при успехе, 0 если индекс неверен
int queue_pos_seek(struct ll_queue_pos* pos, int index);
// Получить текущий элемент без удаления
static inline struct ll_entry* queue_pos_current(struct ll_queue_pos* pos) {
if (!pos || !pos->valid) return NULL;
return pos->current;
}
// Получить текущий индекс
static inline int queue_pos_index(struct ll_queue_pos* pos) {
if (!pos || !pos->valid) return -1;
return pos->index;
}
// Обновить позицию после изменения очереди (сбросить версию)
static inline void queue_pos_refresh(struct ll_queue_pos* pos) {
if (!pos || !pos->queue) return;
pos->version = pos->queue->version;
pos->valid = 1;
}
// Вставить элемент относительно текущей позиции
// mode: 0 = перед current, 1 = после current
// Возвращает: 0 при успехе, -1 при ошибке
int queue_pos_insert(struct ll_queue_pos* pos, struct ll_entry* entry, int mode);
// Удалить элемент из текущей позиции и вернуть его
// После удаления current указывает на следующий элемент (или NULL)
// Возвращает: указатель на удаленный элемент или NULL
struct ll_entry* queue_pos_remove(struct ll_queue_pos* pos);
// Удалить конкретный элемент из очереди по указателю
// Возвращает: 0 при успехе, -1 если элемент не найден
int queue_entry_remove(struct ll_queue* q, struct ll_entry* entry);
#endif // LL_QUEUE_H

210
lib/ll_queue.h_

@ -0,0 +1,210 @@
#ifndef LL_QUEUE_H
#define LL_QUEUE_H
#include <stddef.h> // для size_t
#include "memory_pool.h" // для struct memory_pool
// Предварительные объявления
struct ll_queue;
struct ll_entry;
struct queue_waiter;
struct memory_pool;
struct ll_queue_pos; // Управляющая структура позиции
// Тип коллбэка: вызывается при добавлении элемента в пустую очередь или для продолжения обработки
// Параметры: указатель на очередь, указатель на элемент (первый в очереди), пользовательский аргумент
typedef void (*queue_callback_fn)(struct ll_queue* q, struct ll_entry* entry, void* arg);
// Структура элемента - переменный размер, данные расположены сразу после структуры
struct ll_entry {
struct ll_entry* next; // Указатель на следующий элемент в очереди
struct ll_entry* prev; // Указатель на предыдущий элемент в очереди
size_t size; // Размер данных элемента (байт)
int ref_count; // Счетчик ссылок для предотвращения double-free
};
// Структура условия ожидания (waiter)
struct queue_waiter {
int max_packets; // Максимальное количество пакетов
size_t max_bytes; // Максимальное количество байт
void (*callback)(struct ll_queue* q, void* arg); // Коллбэк для вызова
void* callback_arg; // Аргумент коллбэка
struct queue_waiter* next; // Следующий ожидающий в списке
};
typedef void (*queue_threshold_callback_fn)(struct ll_queue* q, void* arg);
// Структура очереди
struct ll_queue {
struct ll_entry* head; // Первый элемент (извлекается отсюда)
struct ll_entry* tail; // Последний элемент (добавляется сюда)
int count; // Текущее количество элементов
size_t total_bytes; // Общий размер данных всех элементов (байт)
int size_limit; // Максимальное количество (-1 = без ограничения)
queue_callback_fn callback; // Функция коллбэка
void* callback_arg; // Пользовательский аргумент для коллбэка
int callback_suspended; // 1 если коллбэки приостановлены (во время обработки)
void* resume_timeout_id; // ID таймаута uasync для отложенного возобновления
struct UASYNC* ua; // Экземпляр uasync для таймеров
struct queue_waiter* waiters; // Список ожидающих коллбэков
// Пулы памяти для оптимизации аллокаций
struct memory_pool* pool; // Пул для структур struct queue_waiter
};
// ==================== Управление очередью ====================
// Создать новую пустую очередь
// ua - экземпляр uasync для таймеров (обязательный параметр)
// pool - если не null то использовать этот пул памяти
// Возвращает: указатель на очередь или NULL при ошибке выделения памяти (see memory_pool.c/h)
struct ll_queue* queue_new(struct UASYNC* ua, struct memory_pool* pool);
// Освободить очередь и все её элементы
// Также отменяет отложенное возобновление если оно запланировано
void queue_free(struct ll_queue* q);
// ==================== Конфигурация очереди ====================
// Установить функцию и аргумент коллбэка для автозабора из очереди
// Коллбэк вызывается когда в очереди есть элемент и разрешен коллбэк
// обработчик должен обработать этот пакет (может использовать асинхронное ожидание). Когда будет готов к приёму следующего - должен вызвать resume_callback. обработка строго по одному пакету.
void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
// Возобновить коллбэки после обработки элемента переданного в коллбэке (тянуть дополнительные элементы из очереди не предусмотернные api нельзя).
// эта функция должна вызываться всегда после того как cbk_fn обработала пакет (можно с ожиданием через async), иначе очередь застрянет.
// Если в очереди остались элементы, запланирует вызов коллбэка через uasync_set_timeout(0)
// Это предотвращает накопление рекурсии в стеке вызовов
void queue_resume_callback(struct ll_queue* q);
// Установить максимальное количество элементов в очереди
// При превышении лимита новый элемент автоматически освобождается
void queue_set_size_limit(struct ll_queue* q, int lim);
// ==================== Управление элементами ====================
// Создать новый элемент с областью данных указанного размера
// Память выделяется одним блоком: [struct ll_entry][область данных data_size байт]
// Возвращает: указатель на элемент или NULL при ошибке выделения памяти
struct ll_entry* queue_entry_new(size_t data_size);
// Освободить элемент (не влияет на связи в очереди)
void queue_entry_free(struct ll_entry* entry);
// ==================== Операции с очередью ====================
// Добавить элемент в конец очереди (FIFO)
// Если очередь была пустой и коллбэки разрешены - вызывает коллбэк
// Возвращает: 0 при успехе, -1 если превышен лимит размера (элемент освобожден)
int queue_entry_put(struct ll_queue* q, struct ll_entry* entry);
// Добавить элемент в начало очереди (LIFO, высокий приоритет)
// Если очередь была пустой и коллбэки разрешены - вызывает коллбэк
// Возвращает: 0 при успехе, -1 если превышен лимит размера (элемент освобожден)
int queue_entry_put_first(struct ll_queue* q, struct ll_entry* entry);
// Извлечь элемент из начала очереди
// При извлечении приостанавливает коллбэки (callback_suspended = 1) чтобы предотвратить рекурсию
// Возвращает: указатель на элемент или NULL если очередь пуста
struct ll_entry* queue_entry_get(struct ll_queue* q);
// Получить текущее количество элементов в очереди
int queue_entry_count(struct ll_queue* q);
// ==================== Вспомогательные функции ====================
// Получить указатель на область данных элемента
// Данные расположены сразу после структуры struct ll_entry
static inline void* ll_entry_data(struct ll_entry* entry) {
return (void*)(entry + 1);
}
// Получить размер данных элемента
static inline size_t ll_entry_size(struct ll_entry* entry) {
return entry->size;
}
// ==================== Асинхронное ожидание ====================
// Зарегистрировать одноразовый коллбэк, который будет вызван когда очередь будет иметь
// не более max_packets пакетов и не более max_bytes байт.
// Если условие уже выполнено, коллбэк вызывается немедленно.
// Можно зарегистрировать несколько ожиданий на одной очереди.
// Возвращает указатель на waiter для возможной отмены через queue_cancel_wait
struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes,
queue_threshold_callback_fn callback, void* arg);
// Отменить ожидание (удалить waiter из списка)
void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter);
// Получить общий размер данных в очереди (байт)
static inline size_t queue_total_bytes(struct ll_queue* q) {
if (!q) return 0;
return q->total_bytes;
}
// Получить статистику использования пулов памяти
void queue_get_pool_stats(struct ll_queue* q, size_t* waiter_allocations, size_t* waiter_reuse);
// ==================== Управление позициями ====================
// Структура управления позицией для навигации по очереди
struct ll_queue_pos {
struct ll_queue* queue; // Ссылка на очередь
struct ll_entry* current; // Текущий элемент (NULL = перед head)
struct ll_entry* prev; // Предыдущий элемент (для навигации)
int index; // Текущий индекс (-1 = перед head)
int valid; // Флаг валидности позиции
};
// Создать новую позицию для очереди
struct ll_queue_pos* queue_pos_new(struct ll_queue* queue);
// Освободить позицию (не удаляет элементы очереди)
void queue_pos_free(struct ll_queue_pos* pos);
// Сбросить позицию в начальное состояние (перед head)
void queue_pos_reset(struct ll_queue_pos* pos);
// Перейти к следующему элементу
// Возвращает: 1 при успехе, 0 если достигнут конец
int queue_pos_next(struct ll_queue_pos* pos);
// Перейти к предыдущему элементу
// Возвращает: 1 при успехе, 0 если достигнуто начало
int queue_pos_prev(struct ll_queue_pos* pos);
// Перейти к конкретному индексу (0 = head)
// Возвращает: 1 при успехе, 0 если индекс неверен
int queue_pos_seek(struct ll_queue_pos* pos, int index);
// Получить текущий элемент без удаления
static inline struct ll_entry* queue_pos_current(struct ll_queue_pos* pos) {
if (!pos || !pos->valid) return NULL;
return pos->current;
}
// Получить текущий индекс
static inline int queue_pos_index(struct ll_queue_pos* pos) {
if (!pos || !pos->valid) return -1;
return pos->index;
}
// Вставить элемент относительно текущей позиции
// mode: 0 = перед current, 1 = после current
// Возвращает: 0 при успехе, -1 при ошибке
int queue_pos_insert(struct ll_queue_pos* pos, struct ll_entry* entry, int mode);
// Удалить элемент из текущей позиции и вернуть его
// После удаления current указывает на следующий элемент (или NULL)
// Возвращает: указатель на удаленный элемент или NULL
struct ll_entry* queue_pos_remove(struct ll_queue_pos* pos);
// Удалить конкретный элемент из очереди по указателю
// Возвращает: 0 при успехе, -1 если элемент не найден
int queue_entry_remove(struct ll_queue* q, struct ll_entry* entry);
#endif // LL_QUEUE_H

4
src/Makefile.am

@ -9,6 +9,7 @@ utun_SOURCES = \
tun_if.c \
etcp.c \
etcp_connections.c \
etcp_loadbalancer.c \
secure_channel.c \
crc32.c \
pkt_normalizer.c \
@ -34,7 +35,8 @@ utun_CFLAGS = \
utun_LDADD = \
$(top_builddir)/lib/libuasync.a \
-lpthread
-lpthread \
-lm
# Install directories
install-exec-hook:

11
src/config_parser.c

@ -208,6 +208,10 @@ static int parse_global(const char *key, const char *value, struct global_config
global->my_node_id = strtoull(value, NULL, 16);
return 0;
}
if (strcmp(key, "tun_ifname") == 0) {
snprintf(global->tun_ifname, sizeof(global->tun_ifname), "%s", value);
return 0;
}
if (strcmp(key, "tun_ip") == 0) {
uint8_t netmask;
return parse_ip_with_netmask(value, &global->tun_ip, &netmask);
@ -438,6 +442,12 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
cfg->clients = cur_client;
}
// Set default TUN interface name if not provided
if (cfg->global.tun_ifname[0] == '\0') {
snprintf(cfg->global.tun_ifname, sizeof(cfg->global.tun_ifname), "tun0");
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Auto-generated TUN interface name: %s", cfg->global.tun_ifname);
}
return cfg;
error:
@ -512,6 +522,7 @@ void print_config(const struct utun_config *cfg) {
printf(" Private key: %s\n", g->my_private_key_hex);
printf(" Public key: %s\n", g->my_public_key_hex);
printf(" Node ID: %llx\n", (unsigned long long)g->my_node_id);
printf(" TUN interface: %s\n", g->tun_ifname[0] ? g->tun_ifname : "auto");
printf(" TUN IP: %s\n", ip_to_string(&g->tun_ip, ip_buffer, sizeof(ip_buffer)));
printf(" MTU: %d\n", g->mtu);
printf(" Net debug: %d\n", g->net_debug);

1
src/config_parser.h

@ -62,6 +62,7 @@ struct global_config {
char my_private_key_hex[MAX_KEY_LEN];
char my_public_key_hex[MAX_KEY_LEN];
uint64_t my_node_id;
char tun_ifname[16]; // TUN interface name (e.g., "tun12")
struct IP tun_ip;
int mtu;
struct sockaddr_storage control_sock;

928
src/etcp.c

File diff suppressed because it is too large Load Diff

467
src/etcp.c1

@ -0,0 +1,467 @@
// etcp.c - ETCP Protocol Implementation (refactored based on etcp_protocol.txt)
#include "etcp.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
#include "crc32.h"
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
// Constants from spec (adjust as needed)
#define MAX_INFLIGHT_BYTES 65536 // Initial window
#define RETRANS_K1 2.0f // RTT multiplier for retrans timeout
#define RETRANS_K2 1.5f // Jitter multiplier
#define ACK_DELAY_MS 2 // ACK timer delay (in 0.1ms units: 20)
#define BURST_DELAY_FACTOR 4 // Delay before burst
#define BURST_SIZE 5 // Packets in burst (1 delayed + 4 burst)
// Internal functions
static void retrans_timer_cb(void* arg);
static void ack_timer_cb(void* arg);
static void update_rtt(struct ETCP_CONN* etcp, uint16_t new_rtt);
static void detect_gaps_and_request_retrans(struct ETCP_CONN* etcp);
static void process_section_ack(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len);
static void process_section_retrans(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len);
static void process_section_timestamp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len);
static void process_section_payload(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len);
static void process_section_meas_ts(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len);
static void process_section_meas_resp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len);
static void append_optional_sections(struct ETCP_CONN* etcp, uint8_t* buf, uint16_t* len, struct ETCP_LINK* link);
static void deliver_contiguous_packets(struct ETCP_CONN* etcp);
static void retrans_check(struct ETCP_CONN* etcp);
static inflight_packet_t* find_inflight(struct ETCP_CONN* etcp, uint16_t id, uint8_t state);
static void move_to_wait_send(struct ETCP_CONN* etcp, inflight_packet_t* pkt);
static void remove_inflight(struct ETCP_CONN* etcp, inflight_packet_t* pkt);
static void start_burst_measurement(struct ETCP_CONN* etcp, struct ETCP_LINK* link);
static void update_link_bandwidth(struct ETCP_LINK* link, uint32_t new_bw);
static void free_inflight_list(inflight_packet_t** list);
// Create ETCP connection
struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance) {
struct ETCP_CONN* etcp = calloc(1, sizeof(struct ETCP_CONN));
if (!etcp) return NULL;
etcp->instance = instance;
etcp->input_queue = queue_new(instance->ua, instance->pkt_pool);
etcp->output_queue = queue_new(instance->ua, instance->pkt_pool);
etcp->mtu = 1500; // Default
etcp->window_size = MAX_INFLIGHT_BYTES;
etcp->next_tx_id = 1;
// Init crypto (as per original)
if (sc_init_ctx(&etcp->crypto_ctx, &instance->my_keys) != SC_OK) {
etcp_connection_close(etcp);
return NULL;
}
return etcp;
}
// Close connection
void etcp_connection_close(struct ETCP_CONN* etcp) {
if (!etcp) return;
// Cancel timers
if (etcp->retrans_timer) uasync_cancel_timeout(etcp->instance->ua, etcp->retrans_timer);
if (etcp->ack_timer) uasync_cancel_timeout(etcp->instance->ua, etcp->ack_timer);
// Free lists
free_inflight_list(&etcp->wait_ack_list);
free_inflight_list(&etcp->wait_send_list);
rx_packet_t* rx = etcp->rx_list;
while (rx) {
rx_packet_t* next = rx->next;
free(rx->data);
free(rx);
rx = next;
}
queue_free(etcp->input_queue);
queue_free(etcp->output_queue);
free(etcp);
}
static void free_inflight_list(inflight_packet_t** list) {
inflight_packet_t* pkt = *list;
while (pkt) {
inflight_packet_t* next = pkt->next;
free(pkt->data);
free(pkt);
pkt = next;
}
*list = NULL;
}
// Reset connection
void etcp_conn_reset(struct ETCP_CONN* etcp) {
if (!etcp) return;
// Cancel timers
if (etcp->retrans_timer) uasync_cancel_timeout(etcp->instance->ua, etcp->retrans_timer);
if (etcp->ack_timer) uasync_cancel_timeout(etcp->instance->ua, etcp->ack_timer);
// Clear lists
free_inflight_list(&etcp->wait_ack_list);
free_inflight_list(&etcp->wait_send_list);
rx_packet_t* rx = etcp->rx_list;
while (rx) {
rx_packet_t* next = rx->next;
free(rx->data);
free(rx);
rx = next;
}
etcp->rx_list = NULL;
// Reset IDs and state
etcp->next_tx_id = 1;
etcp->last_rx_id = 0;
etcp->last_delivered_id = 0;
etcp->unacked_bytes = 0;
etcp->pending_ack_count = 0;
etcp->pending_retrans_count = 0;
etcp->rtt_last = 0;
etcp->rtt_avg_10 = 0;
etcp->rtt_avg_100 = 0;
etcp->jitter = 0;
etcp->burst_in_progress = 0;
// Clear queues but keep them
// Note: queue_clear not in original, but per spec to reset unconfirmed
// Assume queues are cleared externally if needed
}
// Input decrypted packet from connections
void etcp_conn_input(struct ETCP_DGRAM* pkt) {
if (!pkt || !pkt->link || !pkt->link->etcp) return;
struct ETCP_CONN* etcp = pkt->link->etcp;
uint8_t* data = pkt->data;
uint16_t len = pkt->data_len;
uint16_t ts = pkt->timestamp;
// Update last activity
pkt->link->last_recv_local_time = get_current_time_units();
pkt->link->last_recv_timestamp = ts;
// Parse sections (per spec: scan sections)
uint16_t pos = 0;
while (pos < len) {
uint8_t type = data[pos];
uint16_t sec_len = (data[pos+1] << 8) | data[pos+2]; // Assume 3-byte header: type(1) + len(2)
pos += 3;
if (pos + sec_len > len) break; // Invalid
uint8_t* sec_data = data + pos;
switch (type) {
case ETCP_SECTION_ACK:
process_section_ack(etcp, sec_data, sec_len);
break;
case ETCP_SECTION_RETRANS:
process_section_retrans(etcp, sec_data, sec_len);
break;
case ETCP_SECTION_TIMESTAMP:
process_section_timestamp(etcp, sec_data, sec_len);
break;
case ETCP_SECTION_PAYLOAD:
process_section_payload(etcp, sec_data, sec_len);
break;
case ETCP_SECTION_MEAS_TS:
process_section_meas_ts(etcp, sec_data, sec_len);
break;
case ETCP_SECTION_MEAS_RESP:
process_section_meas_resp(etcp, sec_data, sec_len);
break;
default:
// Ignore unknown
break;
}
pos += sec_len;
}
// After processing
detect_gaps_and_request_retrans(etcp);
deliver_contiguous_packets(etcp);
// If wait_timeout_active and flag set (from spec)
if (etcp->wait_timeout_active) {
// Trigger etcp_request_pkt if needed
// For now, assume load balancer polls
}
}
// Request next packet for load balancer
struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
if (!etcp) return NULL;
// Check if waiting (per spec)
if (etcp->pending_retrans_count == 0 && queue_entry_count(etcp->input_queue) == 0) {
// Wait input_queue
queue_wait_threshold(etcp->input_queue, 0, 0, NULL, etcp); // Placeholder callback to resume
return NULL;
}
if (etcp->unacked_bytes >= etcp->window_size) {
// Wait SACK or timeout
etcp->wait_timeout_active = 1;
return NULL;
}
// Find packet to send (per spec)
inflight_packet_t* pkt = etcp->wait_send_list; // Prefer retrans
if (!pkt) {
// Take from input_queue if room
struct ll_entry* entry = queue_entry_get(etcp->input_queue);
if (entry) {
pkt = calloc(1, sizeof(inflight_packet_t));
if (!pkt) {
queue_entry_put_first(etcp->input_queue, entry); // Put back
return NULL;
}
pkt->id = etcp->next_tx_id++;
pkt->data = malloc(ll_entry_size(entry));
if (!pkt->data) {
free(pkt);
queue_entry_put_first(etcp->input_queue, entry);
return NULL;
}
memcpy(pkt->data, ll_entry_data(entry), ll_entry_size(entry));
pkt->data_len = ll_entry_size(entry);
pkt->payload_len = pkt->data_len; // Assume
pkt->state = INFLIGHT_STATE_WAIT_SEND;
// Add to wait_send_list
pkt->next = etcp->wait_send_list;
etcp->wait_send_list = pkt;
queue_entry_free(entry);
} else {
return NULL;
}
}
// Select link (call load balancer)
struct ETCP_LINK* link = etcp_loadbalancer_select_link(etcp); // To implement in loadbalancer
if (!link) return NULL;
// Build packet
uint8_t buf[1600];
uint16_t len = 0;
// Append optional sections
append_optional_sections(etcp, buf, &len, link);
// Append payload section
buf[len++] = ETCP_SECTION_PAYLOAD;
buf[len++] = (pkt->payload_len >> 8) & 0xFF;
buf[len++] = pkt->payload_len & 0xFF;
memcpy(buf + len, pkt->data + (pkt->data_len - pkt->payload_len), pkt->payload_len); // Assume payload at end
len += pkt->payload_len;
// Prepare DGRAM
struct ETCP_DGRAM* dgram = memory_pool_alloc(etcp->instance->pkt_pool);
if (!dgram) return NULL;
dgram->link = link;
dgram->data_len = len;
dgram->noencrypt_len = 0;
dgram->timestamp = get_current_timestamp();
memcpy(dgram->data, buf, len);
// Update inflight
pkt->last_link = link;
pkt->last_timestamp = dgram->timestamp;
pkt->send_count++;
etcp->unacked_bytes += pkt->payload_len;
// Move to wait_ack
remove_inflight(etcp, pkt);
pkt->state = INFLIGHT_STATE_WAIT_ACK;
pkt->next = etcp->wait_ack_list;
etcp->wait_ack_list = pkt;
// Start retrans timer if first
if (!etcp->retrans_timer) {
etcp->retrans_timer = uasync_set_timeout(etcp->instance->ua, 20, etcp, retrans_timer_cb); // 2ms
}
return dgram;
}
// Placeholder implementations for sections
static void append_optional_sections(struct ETCP_CONN* etcp, uint8_t* buf, uint16_t* len, struct ETCP_LINK* link) {
// Add ACK, RETRANS, TIMESTAMP
// Example for ACK
if (etcp->pending_ack_count > 0) {
// Build ACK section
// ...
etcp->pending_ack_count = 0;
}
// Similarly for others
}
static void process_section_ack(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) {
// Parse ACKs, mark in inflight, remove if acked
// Update unacked_bytes
// Set lp_ts if needed for retrans check
}
static void process_section_retrans(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) {
// Parse IDs, mark need_retrans, move to wait_send
}
static void process_section_timestamp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) {
// Update RTT
uint16_t rtt = timestamp_diff(get_current_timestamp(), *(uint16_t*)data); // Assume format
update_rtt(etcp, rtt);
}
static void process_section_payload(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) {
// Extract ID from payload header (assume first 2 bytes ID)
uint16_t id = (data[0] << 8) | data[1];
if (id <= etcp->last_rx_id) return; // Duplicate
// Insert into rx_list sorted by ID
rx_packet_t* new_rx = calloc(1, sizeof(rx_packet_t));
new_rx->id = id;
new_rx->timestamp = get_current_timestamp();
new_rx->data = malloc(len - 2);
memcpy(new_rx->data, data + 2, len - 2);
new_rx->data_len = len - 2;
// Insert sorted
rx_packet_t** ptr = &etcp->rx_list;
while (*ptr && (*ptr)->id < id) ptr = &(*ptr)->next;
new_rx->next = *ptr;
*ptr = new_rx;
if (id > etcp->last_rx_id) etcp->last_rx_id = id;
// Add to pending ACK
if (etcp->pending_ack_count < 32) {
etcp->pending_ack_ids[etcp->pending_ack_count] = id;
etcp->pending_ack_ts[etcp->pending_ack_count++] = new_rx->timestamp;
}
// Schedule ACK if not
if (!etcp->ack_timer) {
etcp->ack_timer = uasync_set_timeout(etcp->instance->ua, ACK_DELAY_MS * 10, etcp, ack_timer_cb);
}
}
static void process_section_meas_ts(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) {
// Send meas_resp for each
// ...
}
static void process_section_meas_resp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) {
// Calculate bandwidth, update link
// ...
}
static void detect_gaps_and_request_retrans(struct ETCP_CONN* etcp) {
// Scan rx_list for gaps > last_delivered_id
// Add to pending_retrans_ids
}
static void deliver_contiguous_packets(struct ETCP_CONN* etcp) {
rx_packet_t* rx = etcp->rx_list;
while (rx && rx->id == etcp->last_delivered_id + 1) {
// Deliver to output_queue
struct ll_entry* entry = queue_entry_new(rx->data_len);
memcpy(ll_entry_data(entry), rx->data, rx->data_len);
queue_entry_put(etcp->output_queue, entry);
etcp->last_delivered_id = rx->id;
rx_packet_t* next = rx->next;
free(rx->data);
free(rx);
rx = next;
}
etcp->rx_list = rx;
}
static void retrans_check(struct ETCP_CONN* etcp) {
uint64_t now = get_current_time_units();
inflight_packet_t* pkt = etcp->wait_ack_list;
uint16_t lp_ts = 0;
while (pkt) {
uint16_t timeout = (uint16_t)(etcp->rtt_avg_10 * RETRANS_K1 + etcp->jitter * RETRANS_K2);
if (timestamp_diff(now, pkt->last_timestamp) > timeout) {
move_to_wait_send(etcp, pkt);
}
// ... (lp_ts logic per spec)
pkt = pkt->next;
}
// Reschedule timer
etcp->retrans_timer = uasync_set_timeout(etcp->instance->ua, 20, etcp, retrans_timer_cb);
}
static void move_to_wait_send(struct ETCP_CONN* etcp, inflight_packet_t* pkt) {
remove_inflight(etcp, pkt);
pkt->next = etcp->wait_send_list;
etcp->wait_send_list = pkt;
pkt->state = INFLIGHT_STATE_WAIT_SEND;
}
static void remove_inflight(struct ETCP_CONN* etcp, inflight_packet_t* pkt) {
// Remove from list (wait_ack or wait_send)
inflight_packet_t** list = (pkt->state == INFLIGHT_STATE_WAIT_ACK) ? &etcp->wait_ack_list : &etcp->wait_send_list;
inflight_packet_t** ptr = list;
while (*ptr && *ptr != pkt) ptr = &(*ptr)->next;
if (*ptr) *ptr = pkt->next;
}
static inflight_packet_t* find_inflight(struct ETCP_CONN* etcp, uint16_t id, uint8_t state) {
inflight_packet_t* list = (state == INFLIGHT_STATE_WAIT_ACK) ? etcp->wait_ack_list : etcp->wait_send_list;
while (list) {
if (list->id == id) return list;
list = list->next;
}
return NULL;
}
static void retrans_timer_cb(void* arg) {
struct ETCP_CONN* etcp = arg;
retrans_check(etcp);
}
static void ack_timer_cb(void* arg) {
struct ETCP_CONN* etcp = arg;
// Trigger send of ACK packet (via load balancer or direct)
// For now, assume appended in next pkt
etcp->ack_timer = NULL;
}
static void update_rtt(struct ETCP_CONN* etcp, uint16_t new_rtt) {
etcp->rtt_last = new_rtt;
etcp->rtt_avg_10 = (etcp->rtt_avg_10 * 9 + new_rtt) / 10;
etcp->rtt_avg_100 = (etcp->rtt_avg_100 * 99 + new_rtt) / 100;
// Jitter: max-min last 10 (implement history if needed)
}
static void start_burst_measurement(struct ETCP_CONN* etcp, struct ETCP_LINK* link) {
// Initiate burst (per spec)
etcp->burst_in_progress = 1;
etcp->burst_start_id = etcp->next_tx_id;
// Schedule delayed packets
}
static void update_link_bandwidth(struct ETCP_LINK* link, uint32_t new_bw) {
// Update bandwidth
// link->bandwidth = new_bw; // Add to ETCP_LINK
}
// Get stats (original)
void etcp_get_stats(struct ETCP_CONN* etcp, size_t* packets_sent, size_t* packets_recv,
size_t* pool_allocs, size_t* pool_reuse) {
if (packets_sent) *packets_sent = etcp->bytes_sent_total / 1000; // Approx
if (packets_recv) *packets_recv = etcp->bytes_received_total / 1000;
memory_pool_get_stats(etcp->instance->pkt_pool, pool_allocs, pool_reuse);
}
uint16_t get_current_timestamp() {
struct timeval tv;
gettimeofday(&tv, NULL);
return (uint16_t)((tv.tv_sec * 1000 + tv.tv_usec / 1000) & 0xFFFF); // ms wrap
}

489
src/etcp.c_

@ -0,0 +1,489 @@
// etcp.c - ETCP Protocol Implementation
#include "../lib/u_async.h"
#include "utun_instance.h"
#include "etcp.h"
#include "../lib/debug_config.h"
#include "../lib/ll_queue.h"
#include "crc32.h"
#include "secure_channel.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/time.h>
#include <time.h>
// Service packet headers (from protocol, but reset/init handled elsewhere)
#define ETCP_ACK_HEADER 0x01
#define ETCP_RETRANS_HEADER_BASE 0x10 // 0x10 to 0x2F for retrans requests
#define ETCP_PAYLOAD_HEADER 0x00
#define ETCP_INIT_RESPONSE 0x03
// Forward declarations of internal functions (adapted from etcp_master)
static void retransmit_check(struct ETCP_CONN* etcp);
static void update_metrics(struct ETCP_CONN* etcp, uint16_t rtt);
static uint16_t timestamp_diff(uint16_t t1, uint16_t t2);
static int id_compare(uint16_t id1, uint16_t id2);
static void retransmit_packet(struct ETCP_CONN* etcp, uint16_t id);
// Timer callbacks
static void tx_timer_callback(void* arg);
static void retransmit_timer_callback(void* arg);
// Internal queue callbacks
static void tx_queue_callback(struct ll_queue* q, struct ll_entry* entry, void* arg);
static void schedule_ack_timer(struct ETCP_CONN* etcp);
static void request_retransmission_for_gaps(struct ETCP_CONN* etcp);
static void queue_clear(struct ll_queue* q) {
struct ll_entry* entry;
while ((entry = queue_entry_get(q)) != NULL) {
queue_entry_free(entry);
}
}
static void etcp_update_window(struct ETCP_CONN* etcp) {
etcp->window_size = 65536; // Initial window size
etcp->retrans_timer_period = 20; // Default retransmit period
}
// Reset connection (adapted from etcp_reset in master, without packet sending)
void etcp_conn_reset(struct ETCP_CONN* etcp) {
if (!etcp) return;
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_conn_reset: resetting instance");
// Cancel timers
if (etcp->next_tx_timer) {
uasync_cancel_timeout(etcp->instance->ua, etcp->next_tx_timer);
etcp->next_tx_timer = NULL;
}
if (etcp->retransmit_timer) {
uasync_cancel_timeout(etcp->instance->ua, etcp->retransmit_timer);
etcp->retransmit_timer = NULL;
}
// Clear queues (but keep them)
queue_clear(etcp->input_queue);
queue_clear(etcp->output_queue);
// Clear lists
rx_packet_t* rx = etcp->rx_list;
while (rx) {
rx_packet_t* next = rx->next;
if (rx->data) free(rx->data);
free(rx);
rx = next;
}
etcp->rx_list = NULL;
sent_packet_t* sent = etcp->sent_list;
while (sent) {
sent_packet_t* next = sent->next;
if (sent->data) free(sent->data);
free(sent);
sent = next;
}
etcp->sent_list = NULL;
// Reset metrics and stats
etcp->rtt_last = 0;
etcp->rtt_avg_10 = 0;
etcp->rtt_avg_100 = 0;
etcp->jitter = 0;
etcp->bytes_sent_total = 0;
etcp->retransmissions_count = 0;
etcp->ack_packets_count = 0;
etcp->control_packets_count = 0;
etcp->total_packets_sent = 0;
etcp->unique_packets_sent = 0;
etcp->bytes_received_total = 0;
// Reset IDs
etcp->next_tx_id = 1;
etcp->last_sent_id = 0;
etcp->last_rx_id = 0;
etcp->last_delivered_id = 0;
// Reset history
etcp->rtt_history_idx = 0;
etcp->rtt_history_count = 0;
// Reset pending
etcp->pending_ack_count = 0;
etcp->pending_retransmit_count = 0;
// Reset window
etcp->unacked_bytes = 0;
etcp->last_acked_id = 0;
etcp->last_rx_ack_id = 0;
etcp->next_retrans_time = 0;
etcp->window_blocked = 0;
// Reset forward progress
etcp->oldest_missing_id = 0;
etcp->missing_since_time = 0;
etcp_update_window(etcp);
}
// Creating ETCP instance
// после создания надо добавить peer bublic key.
struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance) {
if (!instance) return NULL;
struct ETCP_CONN* etcp = calloc(1, sizeof(struct ETCP_CONN));
if (!etcp) return NULL;
etcp->mtu = 1500; // Default MTU
etcp->instance = instance;
// Initialize crypto context
if (sc_init_ctx(&etcp->crypto_ctx, &etcp->instance->my_keys) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "etcp_connection_create: failed to initialize crypto context for node %llu", (unsigned long long)instance->node_id);
free(etcp);
return NULL;
}
// Initialize queues (tx_queue is input_queue)
etcp->input_queue = queue_new(instance->ua,NULL);
if (!etcp->input_queue) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "etcp_connection_create: failed to create input queue for node %llu", (unsigned long long)instance->node_id);
free(etcp);
return NULL;
}
etcp->output_queue = queue_new(instance->ua,NULL);
if (!etcp->output_queue) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "etcp_connection_create: failed to create output queue for node %llu", (unsigned long long)instance->node_id);
queue_free(etcp->input_queue);
free(etcp);
return NULL;
}
// Set callback for input_queue (tx_queue)
queue_set_callback(etcp->input_queue, tx_queue_callback, etcp);
// Initialize state (from master)
etcp->bandwidth = 10000; // Default: 10000 bytes per timebase (0.1us)
etcp->last_sent_timestamp = get_current_timestamp();
etcp->bytes_allowed = 0;
// Reset all other fields to initial state
etcp_conn_reset(etcp);
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_connection_create: created instance with node_id=%llu, mtu=%d",
(unsigned long long)etcp->instance->node_id, etcp->mtu);
etcp->next=instance->connections;
instance->connections=etcp;
instance->connections_count++;
return etcp;
}
// Destroying ETCP instance
void etcp_connection_close(struct ETCP_CONN* etcp) {
if (!etcp) return;
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_connection_close: destroying instance");
// Cancel timers
if (etcp->next_tx_timer) {
uasync_cancel_timeout(etcp->instance->ua, etcp->next_tx_timer);
etcp->next_tx_timer = NULL;
}
if (etcp->retransmit_timer) {
uasync_cancel_timeout(etcp->instance->ua, etcp->retransmit_timer);
etcp->retransmit_timer = NULL;
}
// Free queues
if (etcp->input_queue) queue_free(etcp->input_queue);
if (etcp->output_queue) queue_free(etcp->output_queue);
// Free rx_list
rx_packet_t* rx = etcp->rx_list;
while (rx) {
rx_packet_t* next = rx->next;
if (rx->data) free(rx->data);
free(rx);
rx = next;
}
// Free sent_list
sent_packet_t* sent = etcp->sent_list;
while (sent) {
sent_packet_t* next = sent->next;
if (sent->data) free(sent->data);
free(sent);
sent = next;
}
free(etcp);
}
// Process incoming packet (partial, truncated in original)
void etcp_conn_input(struct ETCP_DGRAM* pkt) {
if (!pkt) return;
struct ETCP_CONN* etcp=pkt->link->etcp;
if (!etcp) return;
uint8_t* data = pkt->data;
size_t len = pkt->data_len;
if (len < 4) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_conn_input: packet too short (%zu bytes) from node %llu", len, (unsigned long long)etcp->instance->node_id);
return; // Min header
}
uint16_t id = (data[0] << 8) | data[1];
uint16_t timestamp = (data[2] << 8) | data[3];
// Check for INIT_RESPONSE packet (special control packet)
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[ETCP DEBUG] etcp_conn_input: id=%u, len=%zu, first bytes: %02x %02x %02x %02x %02x",
id, len, data[0], data[1], data[2], data[3], data[4]);
data += 4; len -= 4; // Skip header for regular packet processing
int has_payload = 0;
uint8_t* payload_data = NULL;
size_t payload_len = 0;
int is_duplicate = 0;
while (len > 0) {
uint8_t section_header = *data++;
len--;
if (section_header == ETCP_ACK_HEADER) {
// ACK section
uint8_t count = *data++;
len--;
for (uint8_t i = 0; i < count; i++) {
uint16_t ack_id = (data[0] << 8) | data[1];
uint16_t ack_ts = (data[2] << 8) | data[3];
data += 4; len -= 4;
// Process ACK: remove from sent_list, update window
sent_packet_t** ptr = &etcp->sent_list;
while (*ptr) {
if ((*ptr)->id == ack_id) {
sent_packet_t* to_free = *ptr;
*ptr = to_free->next;
etcp->unacked_bytes -= to_free->payload_len;
update_metrics(etcp, timestamp_diff(get_current_timestamp(), ack_ts));
free(to_free->data);
free(to_free);
break;
}
ptr = &(*ptr)->next;
}
}
// last_delivered and last_rx
uint16_t last_delivered = (data[0] << 8) | data[1];
uint16_t last_rx = (data[2] << 8) | data[3];
data += 4; len -= 4;
etcp->last_rx_ack_id = last_rx;
} else if ((section_header & 0xF0) == ETCP_RETRANS_HEADER_BASE) {
// Retrans request
uint8_t count = (section_header & 0x0F) + 1;
for (uint8_t i = 0; i < count; i++) {
uint16_t retrans_id = (data[0] << 8) | data[1];
data += 2;
len -= 2;
retransmit_packet(etcp, retrans_id);
}
// last_delivered and last_rx
uint16_t last_delivered = (data[0] << 8) | data[1];
uint16_t last_rx = (data[2] << 8) | data[3];
data += 4; len -= 4;
} else if (section_header == ETCP_PAYLOAD_HEADER) {
// Payload section
has_payload = 1;
payload_data = data;
payload_len = len;
break; // Payload is last
}
}
// Check for duplicate
rx_packet_t* rx = etcp->rx_list;
while (rx) {
if (rx->id == id) {
is_duplicate = 1;
break;
}
rx = rx->next;
}
if (is_duplicate) {
// Add to pending ACKs
if (etcp->pending_ack_count < 32) {
etcp->pending_ack_ids[etcp->pending_ack_count] = id;
etcp->pending_ack_timestamps[etcp->pending_ack_count] = timestamp;
etcp->pending_ack_count++;
}
schedule_ack_timer(etcp);
return;
}
// Insert into rx_list (sorted)
rx_packet_t* new_rx = calloc(1, sizeof(rx_packet_t));
if (!new_rx) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "etcp_conn_input: failed to allocate memory for rx packet from node %llu", (unsigned long long)etcp->instance->node_id);
return;
}
new_rx->id = id;
new_rx->timestamp = timestamp;
new_rx->has_payload = has_payload;
if (has_payload) {
new_rx->data = malloc(payload_len);
if (!new_rx->data) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "etcp_conn_input: failed to allocate %zu bytes for payload data from node %llu", payload_len, (unsigned long long)etcp->instance->node_id);
free(new_rx);
return;
}
memcpy(new_rx->data, payload_data, payload_len);
new_rx->data_len = payload_len;
etcp->bytes_received_total += payload_len;
}
// Insert sorted
rx_packet_t** ptr = &etcp->rx_list;
while (*ptr && id_compare((*ptr)->id, id) < 0) {
ptr = &(*ptr)->next;
}
new_rx->next = *ptr;
*ptr = new_rx;
// Update last_rx_id
if (id_compare(id, etcp->last_rx_id) > 0) {
etcp->last_rx_id = id;
}
// Add to pending ACKs
if (etcp->pending_ack_count < 32) {
etcp->pending_ack_ids[etcp->pending_ack_count] = id;
etcp->pending_ack_timestamps[etcp->pending_ack_count] = timestamp;
etcp->pending_ack_count++;
}
// Request retrans for gaps and deliver contiguous
request_retransmission_for_gaps(etcp);
schedule_ack_timer(etcp);
}
// Getting statistics (extended with master stats)
void etcp_get_stats(struct ETCP_CONN* etcp, size_t* packets_sent, size_t* packets_recv,
size_t* pool_allocs, size_t* pool_reuse) {
if (!etcp) return;
if (packets_sent) *packets_sent = etcp->total_packets_sent;
if (packets_recv) *packets_recv = etcp->bytes_received_total / 100; // Approximate
if (pool_allocs || pool_reuse) {
memory_pool_get_stats(etcp->instance->pkt_pool, pool_allocs, pool_reuse);
}
}
// ... (rest of functions from master, adapted to ETCP_CONN and using etcp_link_send for tx)
// For tx: assume primary channel (etcp->channels), or iterate if multi-path
static void send_etcp_packet(struct ETCP_CONN* etcp, uint8_t* pkt, uint16_t len) {
if (!etcp->links) {
DEBUG_WARN(DEBUG_CATEGORY_CONNECTION, "send_etcp_packet: no links available for node %llu", (unsigned long long)etcp->instance->node_id);
return; // No links
}
// Send to primary link (or iterate for multi-path)
char dg_mem[1600];
struct ETCP_DGRAM* dg=(struct ETCP_DGRAM*)&dg_mem;
dg->data_len=len;
dg->noencrypt_len=0;
dg->link=etcp->links;// first link TODO: добавить отправку по нескольким линкам
etcp_encrypt_send(dg);
}
static void update_metrics(struct ETCP_CONN* etcp, uint16_t rtt) {
etcp->rtt_last = rtt;
// Update averages, jitter, etc.
// Placeholder: etcp->rtt_avg_10 = (etcp->rtt_avg_10 * 9 + rtt) / 10;
// etcp->rtt_avg_100 = (etcp->rtt_avg_100 * 99 + rtt) / 100;
}
uint64_t get_current_time_units() {
struct timeval tv;
gettimeofday(&tv, NULL);
return ((uint64_t)tv.tv_sec * 10000ULL) + (tv.tv_usec / 100);
}
uint16_t get_current_timestamp() {
return (uint16_t)get_current_time_units();
}
static uint16_t timestamp_diff(uint16_t t1, uint16_t t2) {
if (t1 >= t2) return t1 - t2;
return (0xFFFF - t2) + t1 + 1; // Wrap around
}
static int id_compare(uint16_t id1, uint16_t id2) {
int16_t diff = id1 - id2;
if (diff == 0) return 0;
return (diff > 0) ? 1 : -1; // Simple for now, add wrap logic if needed
}
static void retransmit_packet(struct ETCP_CONN* etcp, uint16_t id) {
sent_packet_t* sent = etcp->sent_list;
while (sent) {
if (sent->id == id) {
send_etcp_packet(etcp, sent->data, sent->data_len);
etcp->retransmissions_count++;
break;
}
sent = sent->next;
}
}
static void tx_queue_callback(struct ll_queue* q, struct ll_entry* entry, void* arg) {
(void)q;
struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg;
// Process entry to send packet
// Placeholder: uint8_t* data = ll_entry_data(entry);
// size_t len = ll_entry_size(entry);
// // Build packet and send
// send_etcp_packet(etcp, data, len);
queue_resume_callback(q);
}
static void schedule_ack_timer(struct ETCP_CONN* etcp) {
// Placeholder: set timeout to send ACKs
}
static void request_retransmission_for_gaps(struct ETCP_CONN* etcp) {
// Detect gaps in rx_list and add to pending_retransmit
}
static void retransmit_check(struct ETCP_CONN* etcp) {
// Check sent_list for timeouts
}
static void tx_timer_callback(void* arg) {
struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg;
// Handle next tx
}
static void retransmit_timer_callback(void* arg) {
struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg;
retransmit_check(etcp);
}

170
src/etcp.h

@ -1,10 +1,10 @@
// etcp.c/h - стек ретрансмиссий для ETCP Protocol
// каждое подключение имеет несколько дублирующих каналов. они реализованы в etcp_connections.c/h
// etcp.h - ETCP Protocol Header (refactored based on etcp_protocol.txt)
#ifndef ETCP_H
#define ETCP_H
#include "etcp_connections.h"
#include "secure_channel.h"
#include "../lib/ll_queue.h"
#include <stdint.h>
#ifdef __cplusplus
@ -13,126 +13,132 @@ extern "C" {
// Forward declarations
struct UTUN_INSTANCE;
// Internal structures (integrated from etcp_master)
struct UASYNC;
// ETCP packet section types (from protocol spec)
#define ETCP_SECTION_PAYLOAD 0x00 // Data payload
#define ETCP_SECTION_ACK 0x01 // ACK section
#define ETCP_SECTION_RETRANS 0x10 // Retransmission request base (0x10-0x2F)
#define ETCP_SECTION_TIMESTAMP 0x06 // Channel timestamp (example, adjust if needed)
#define ETCP_SECTION_MEAS_TS 0x07 // Measurement timestamp for bandwidth
#define ETCP_SECTION_MEAS_RESP 0x08 // Measurement response
// Inflight packet states
#define INFLIGHT_STATE_WAIT_ACK 0
#define INFLIGHT_STATE_WAIT_SEND 1
// Inflight packet structure
typedef struct inflight_packet {
struct inflight_packet* next;
uint16_t id; // Packet ID
struct ETCP_LINK* last_link; // Last sent link
uint16_t last_timestamp; // Last send timestamp
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 need_retrans; // Flag for forced retrans
uint8_t* data; // Packet data
uint16_t data_len; // Data length
uint16_t payload_len; // Payload length for window
} inflight_packet_t;
// RX packet for assembly linked list
typedef struct rx_packet {
struct rx_packet* next;
uint16_t id;
uint16_t timestamp;
uint8_t* data;
uint8_t* data; // Assembled data (payload only)
uint16_t data_len;
uint8_t has_payload;
} rx_packet_t;
typedef struct sent_packet {
struct sent_packet* next;
uint16_t id;
uint16_t timestamp;
uint8_t* data;
uint16_t data_len; // Total packet length
uint16_t payload_len; // Payload length (for window accounting)
uint16_t send_time;
uint8_t need_ack;
uint8_t need_retransmit;
} sent_packet_t;
// ETCP connection structure (refactored)
struct ETCP_CONN {
struct ETCP_CONN* next;
int mtu;
struct UTUN_INSTANCE* instance;
// Каналы (соединения) - linked list
// Links (channels) - linked list
struct ETCP_LINK* links;
// Криптография и состояние
// Crypto and state
struct secure_channel crypto_ctx;
// Информация о пире (для клиентов)
uint64_t peer_node_id; // Node ID пира
// Peer info
uint64_t peer_node_id; // Peer node ID
// Queues
struct ll_queue* input_queue; // Incoming packets to send
struct ll_queue* output_queue; // Assembled outgoing packets
// Inflight lists (two lists as per spec)
inflight_packet_t* wait_ack_list; // Waiting for ACK
inflight_packet_t* wait_send_list; // Waiting for send (retrans)
struct ll_queue* input_queue; // отправитель -> input_queue -> etcp -> etcp_channel -> отправка (etcp_sockets)
struct ll_queue* output_queue; // etcp_sockets -> etcp_channel -> etcp -> output_queue -> получатель
// RX assembly list
rx_packet_t* rx_list; // Sorted by ID for gap detection
// Integrated from etcp_master (retransmission logic)
rx_packet_t* rx_list;
sent_packet_t* sent_list;
// IDs and state
uint16_t next_tx_id; // Next TX ID
uint16_t last_rx_id; // Last received ID
uint16_t last_delivered_id; // Last delivered to output_queue
// Metrics
// Pending ACKs and retrans
uint16_t pending_ack_ids[32];
uint16_t pending_ack_ts[32]; // Timestamps for ACKs
uint8_t pending_ack_count;
uint16_t pending_retrans_ids[32];
uint8_t pending_retrans_count;
// Metrics (RTT, jitter, etc.)
uint16_t rtt_last;
uint16_t rtt_avg_10;
uint16_t rtt_avg_100;
uint16_t jitter;
uint16_t bandwidth; // Bytes per timebase (0.1us)
uint32_t bytes_sent_total;
uint16_t last_sent_timestamp;
uint32_t bytes_allowed;
// Statistics (extended existing get_stats)
uint32_t retransmissions_count;
uint32_t ack_packets_count;
uint32_t control_packets_count;
uint32_t total_packets_sent;
uint32_t unique_packets_sent;
uint32_t bytes_received_total;
uint32_t retransmissions_count;
// State
uint16_t next_tx_id;// инкрементируется после отправки пакета
uint16_t last_sent_id;
uint16_t last_rx_id;
uint16_t last_delivered_id;
// Window and inflight management
uint32_t unacked_bytes; // Current inflight bytes
uint32_t window_size; // Receive window
uint32_t optimal_inflight; // Sum over links
// Timers
void* next_tx_timer;
void* retransmit_timer;
// RTT history
uint16_t rtt_history[100];
uint8_t rtt_history_idx;
uint8_t rtt_history_count;
// Pending ACKs
uint16_t pending_ack_ids[32];
uint16_t pending_ack_timestamps[32];
uint8_t pending_ack_count;
// Pending retransmits
uint16_t pending_retransmit_ids[32];
uint8_t pending_retransmit_count;
// Window management
uint32_t unacked_bytes;
uint32_t window_size;
uint16_t last_acked_id;
uint16_t last_rx_ack_id;
uint16_t retrans_timer_period; // Default 20 (2ms)
uint16_t next_retrans_time;
uint8_t window_blocked;
// Forward progress
uint16_t oldest_missing_id;
uint16_t missing_since_time;
// No reset/init here - handled in connections
void* retrans_timer; // Retrans check timer
void* ack_timer; // ACK send timer
// Bandwidth measurement state
uint8_t burst_in_progress; // Burst transmission flag
uint16_t burst_start_id; // Start ID for burst
// ... (add more for meas_ts, meas_resp)
// Statistics counters
uint32_t ack_packets_count; // Count of ACK packets received
uint16_t last_rx_ack_id; // Last ACK ID received
uint16_t rtt_history[10]; // RTT history for jitter calculation (RTT_HISTORY_SIZE=10)
uint8_t rtt_history_idx; // Current index in RTT history
uint32_t total_packets_sent; // Total packets sent counter
// Flags
uint8_t wait_timeout_active; // In wait timeout state
};
// Создание пустого ETCP instance (после надо добавить ключи, каналы)
// Functions
struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance);
// Уничтожение ETCP instance
void etcp_connection_close(struct ETCP_CONN* etcp);
void etcp_conn_reset(struct ETCP_CONN* etcp);
// Обработать входящий пакет (вызывается из etcp_connections)
// Input from etcp_connections (decrypted packet)
void etcp_conn_input(struct ETCP_DGRAM* pkt);
// Сброс соединения (переинициализация)
void etcp_conn_reset(struct ETCP_CONN* etcp);
// Request next packet for load balancer
struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp);
// Получение статистики
// Get stats
void etcp_get_stats(struct ETCP_CONN* etcp, size_t* packets_sent, size_t* packets_recv,
size_t* pool_allocs, size_t* pool_reuse);
uint64_t get_current_time_units();
uint16_t get_current_timestamp(void);
#ifdef __cplusplus

2
src/etcp_connections.h

@ -74,6 +74,8 @@ struct ETCP_LINK {
size_t recv_errors;
size_t total_encrypted;
size_t total_decrypted;
uint32_t bandwidth; // Link bandwidth in bits/sec
};
// INITIALIZATION (создаёт listen-сокеты и подключения из конфига)

95
src/etcp_loadbalancer.c

@ -0,0 +1,95 @@
// etcp_loadbalancer.c - Load Balancer Implementation (based on etcp_protocol.txt)
#include "etcp_loadbalancer.h"
#include "../lib/debug_config.h"
#include "../lib/u_async.h"
#include <stdlib.h>
// Forward declaration
static void init_timeout_cb(void* arg);
// Constants
#define TIMEBASE_NS 100000 // 0.1ms = 100us = 100000ns
#define DELTA_TIME_NS 10000 // Example delta
// Internal
static uint64_t get_current_nanotime() {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 1000000000ULL + ts.tv_nsec;
}
// Select link for transmission (per spec)
struct ETCP_LINK* etcp_loadbalancer_select_link(struct ETCP_CONN* etcp) {
if (!etcp || !etcp->links) return NULL;
struct ETCP_LINK* best = NULL;
uint64_t min_load_time = UINT64_MAX;
struct ETCP_LINK* link = etcp->links;
while (link) {
if (!link->initialized) {
// Initiate connection if client
if (!link->is_server && !link->init_timer) {
// Send INIT (via etcp_encrypt_send with special dgram)
// Set timer
link->init_timer = uasync_set_timeout(etcp->instance->ua, link->init_timeout, link, init_timeout_cb); // Define cb
}
link = link->next;
continue;
}
// Update load time if inactive
uint64_t now_ns = get_current_nanotime();
uint64_t now_tb = now_ns / (TIMEBASE_NS / 10); // To 0.1ms units
if (link->last_activity < now_tb - (DELTA_TIME_NS / (TIMEBASE_NS / 10))) {
link->last_activity = now_tb - (DELTA_TIME_NS / (TIMEBASE_NS / 10));
}
// Check if can send (load time < now)
if (link->last_activity < now_tb) {
if (link->last_activity < min_load_time) {
min_load_time = link->last_activity;
best = link;
}
}
link = link->next;
}
return best;
}
// Init timeout callback (placeholder)
static void init_timeout_cb(void* arg) {
struct ETCP_LINK* link = arg;
// Resend INIT, increment retry
link->init_retry_count++;
if (link->init_retry_count > 5) {
// Fail
etcp_link_close(link);
return;
}
// Resend...
link->init_timeout *= 2; // Backoff
link->init_timer = uasync_set_timeout(link->etcp->instance->ua, link->init_timeout, link, init_timeout_cb);
}
// Bandwidth limit update (called after send)
void etcp_loadbalancer_update_after_send(struct ETCP_LINK* link, size_t pkt_size) {
if (!link || link->bandwidth == 0) return;
// Time to transmit (ns)
double byte_time_ns = 1000000000.0 / (link->bandwidth * 8.0); // bits/sec to byte/ns
uint64_t tx_time_ns = (uint64_t)(pkt_size * byte_time_ns);
// To timebase (0.1ms units)
uint64_t tx_time_tb = tx_time_ns / (TIMEBASE_NS / 10);
// Update last_activity
uint64_t now_tb = get_current_nanotime() / (TIMEBASE_NS / 10);
link->last_activity += tx_time_tb;
if (link->last_activity < now_tb - (DELTA_TIME_NS / (TIMEBASE_NS / 10))) {
link->last_activity = now_tb - (DELTA_TIME_NS / (TIMEBASE_NS / 10));
}
}

20
src/etcp_loadbalancer.h

@ -0,0 +1,20 @@
// etcp_loadbalancer.h - Load Balancer for ETCP Channels
#ifndef ETCP_LOADBALANCER_H
#define ETCP_LOADBALANCER_H
#include "etcp.h"
#ifdef __cplusplus
extern "C" {
#endif
// Functions
struct ETCP_LINK* etcp_loadbalancer_select_link(struct ETCP_CONN* etcp);
// Add more as needed (e.g., update bandwidth)
#ifdef __cplusplus
}
#endif
#endif // ETCP_LOADBALANCER_H

63
src/etcp_protocol.txt

@ -1,63 +0,0 @@
Формат кодограммы:
------------------
Каждая кодограмма состоит из обязательного заголовка и опциональных секций:
1. Обязательный заголовок всего пакета (2 байта) добавляется при передаче, есть во всех пакетах:
[Timestamp high][Timestamp low]
- Timestamp: время отправки в единицах 0.1 мс (циклическое). при переповторах время обновляется
штфруется строго ВСЁ включая ВСЕ заголовки (кроме INIT+publickey). timestamp вставляется ВСЕГДА ВО ВСЕ КОДОГРАММЫ
2. Опциональные секции (одна или несколько) добавляются в etcp.c:
а) Подтверждения (ACK) - заголовок 0x01:
[0x01][count][(id_hi,id_lo,ts_hi,ts_lo)×count][last_delivered_hi,lo][last_rx_hi,lo]
- count: количество пар ID+timestamp (1-32)
- last_delivered_id: последний ID, доставленный получателю
- last_rx_id: последний полученный ID (для синхронизации прогресса)
б) Запросы ретрансмиссии - заголовок 0x10-0x2F:
[0x10+(count-1)][(id_hi,id_lo)×count][last_delivered_hi,lo][last_rx_hi,lo]
- count: (заголовок & 0x0F) + 1 (1-32)
- last_delivered_id, last_rx_id: для синхронизации состояния
в) channel timestamp (добавляется после выбора канала передачи):
[0x0F] [RET_Timestamp high][RET_Timestamp low] [RECV_Timestamp high][RECV_Timestamp low]
- RET_Timestamp: это timestamp последнего принятого пакета по этому каналу ETCP_LINK плюс разница времени между принятием этого пакета и текущим временем. т.е. приёмная сторона по этому timestamp (и зная своё время) может просчитать время в пути туда + обратно
- RECV_Timestamp: это timestamp времени приёма (т.е. отправляем время "когда мы получили этот пакет по локальном урмени") - по нему другая сторона сможет просчитать относительное время задержки в одну сторону (время туда без обратно).
если последний пакет по этому каналу принят сильно давно (прошло более 30000 единиц времени) то channel timestamp не добавляем.
3. Полезная нагрузка - заголовок 0x00 (одна секция и всегда последняя):
[0x00] [ID high][ID low] [данные...] - Данные пакета
- ID: 16-битный циклический номер пакета.
Пакет может содержать несколько опциональных секций (например, ACK + данные).
Установка подключения для канала (реализщуется в etcp_connections):
когда etcp пытается отправить пакет через линк, то если в линке не инициализировано подключение и он клиент - то он отбрасывает этот пакет и запускает процесс установки соединения.
также процесс установки соединения инициируется при добавлении канала (в etcp_link_new) если это client.
процесс установки соединения:
- отправляем init запрос и выставляем таймаут (сохраняем его в struct ETCP_LINK)
- по таймауту повторяем init запросы, ведем счетчик запросов init.
- когда получаем init подтверждение (отправляется в etcp_connections_read_callback) снимаем таймаут и выставляем флаг struct ETCP_LINK initialized=1
Кодограммы с этими секциями обрабатываются в etcp_connections (в этих кодограммах всегда только одна секция). в обязательном заголовке ID не используется, при передаче для порядка =0:
г) Init запрос - заголовок 0x02 (со сбросом etcp сессии) или 0x04 (бес сброса):
[0x02/0x04] [my_node_id 64bit] [my mtu high] [my mty low] [keepalive high] [keepalive low] [my publick key (64 байта, не шифруется)]
- Инициирует новый connection для tcp instance. если tcp instance нет (первое подключение) - создаёт. Между нодами только одно подключение, но можно добавлять каналы.
- Публичный ключ отправляется в конце пакета без шифрования, чтобы получатель мог установить его и расшифровать остальную часть пакета
д) Init подтверждение - заголовок 0x03:
[0x03] [my_node_id 64bit] [my mtu high] [my mty low]
- Подтверждение инициализации
При получении init получатель пакета должен:
- reset ETCP_LINK с этим ip_port если он есть
- создать новый ETCP_CONN с этим node_id. если уже существует подключение с этим node_id - вызвать etcp_reset (функция сброса окон неподтвержденных данных и нумерации)
uasync select -> etcp_connection.c etcp_connections_read_callback: memory_pool_alloc, decrypt (or init) -> etcp_conn_input (сборка, дефрагментация в ETCP_CONN output_queue)
ETCP_CONN output_queue ->

6
src/routing.c

@ -113,7 +113,7 @@ bool routing_table_insert(struct routing_table *table, const struct route_entry
if (!routing_validate_route(table, entry->network, entry->prefix_length, entry->type)) {
char ip_str[16];
ip_to_string(entry->network, ip_str);
printf("[ROUTING] Ignoring invalid route: %s/%d (type: %s)\n",
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "Ignoring invalid route: %s/%d (type: %s)",
ip_str, entry->prefix_length, route_type_to_string(entry->type));
return false;
@ -179,7 +179,7 @@ bool routing_table_lookup(struct routing_table *table, uint32_t dest_ip, struct
char ip_str[16];
ip_to_string(dest_ip, ip_str);
printf("[ROUTING] Found route for %s: %s/%d -> %s\n",
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Found route for %s: %s/%d -> %s",
ip_str, ip_to_string(entry->network, ip_str), entry->prefix_length,
ip_to_string(entry->next_hop_ip, ip_str));
@ -259,7 +259,7 @@ bool enhanced_routing_add_subnet_range(struct routing_table *table, uint32_t net
char ip_str[16];
ip_to_string(network, ip_str);
printf("[ROUTING] Added subnet validation range: %s/%d\n", ip_str, prefix_length);
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Added subnet validation range: %s/%d", ip_str, prefix_length);
return true;
}

4
src/tun_if.c

@ -266,7 +266,7 @@ static uint32_t get_dest_ip(const uint8_t *packet, size_t len) {
// Callback for TUN device read events
static void tun_read_callback(int fd, void* user_arg) {
void tun_read_callback(int fd, void* user_arg) {
struct UTUN_INSTANCE *instance = (struct UTUN_INSTANCE*)user_arg;
uint8_t buffer[MAX_PACKET_SIZE];
@ -333,7 +333,7 @@ void utun_instance_unregister_sockets(struct UTUN_INSTANCE *instance) {
if (instance->tun_socket_id) {
uasync_remove_socket(instance->ua, instance->tun_socket_id);
instance->tun_socket_id = NULL;
DEBUG_INFO(DEBUG_CATEGORY_TUN, "Unregistered TUN socket");
DEBUG_INFO(DEBUG_CATEGORY_TUN, "Unregistered TUN socket: %s", instance->tun.ifname);
}
}

3
src/tun_if.h

@ -96,6 +96,9 @@ int tun_get_config(const char *ifname, struct tun_config *config);
int utun_instance_register_sockets(struct UTUN_INSTANCE *instance);
void utun_instance_unregister_sockets(struct UTUN_INSTANCE *instance);
// TUN callback function
void tun_read_callback(int fd, void* user_arg);
#ifdef __cplusplus
}

39
src/utun.c

@ -44,8 +44,7 @@ typedef struct {
char *config_file;
char *pid_file;
char *log_file;
char *tun_ifname;
char *tun_ip;
char *debug_config;
int foreground;
int help;
} cmd_args_t;
@ -88,6 +87,8 @@ static void parse_args(int argc, char *argv[], cmd_args_t *args) {
memset(args, 0, sizeof(*args));
args->config_file = DEFAULT_CONFIG;
args->pid_file = DEFAULT_PIDFILE;
args->log_file = NULL;
args->debug_config = NULL;
args->foreground = 0;
args->help = 0;
@ -95,8 +96,7 @@ static void parse_args(int argc, char *argv[], cmd_args_t *args) {
{"config", required_argument, 0, 'c'},
{"pidfile", required_argument, 0, 'p'},
{"log", required_argument, 0, 'l'},
{"tun", required_argument, 0, 't'},
{"tun-ip", required_argument, 0, 'i'},
{"debug", required_argument, 0, 'd'},
{"foreground", no_argument, 0, 'f'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
@ -105,7 +105,7 @@ static void parse_args(int argc, char *argv[], cmd_args_t *args) {
int opt;
int option_index = 0;
while ((opt = getopt_long(argc, argv, "c:p:l:t:i:fh",
while ((opt = getopt_long(argc, argv, "c:p:l:d:fh",
long_options, &option_index)) != -1) {
switch (opt) {
case 'c':
@ -117,15 +117,12 @@ static void parse_args(int argc, char *argv[], cmd_args_t *args) {
case 'l':
args->log_file = optarg;
break;
case 't':
args->tun_ifname = optarg;
break;
case 'i':
args->tun_ip = optarg;
break;
case 'f':
args->foreground = 1;
break;
case 'd':
args->debug_config = optarg;
break;
case 'h':
args->help = 1;
break;
@ -144,12 +141,11 @@ static void print_usage(const char *progname) {
printf(" -c, --config FILE Configuration file (default: %s)\n", DEFAULT_CONFIG);
printf(" -p, --pidfile FILE PID file (default: %s)\n", DEFAULT_PIDFILE);
printf(" -l, --log FILE Log file (default: stderr)\n");
printf(" -t, --tun IFNAME TUN interface name (e.g., tun12)\n");
printf(" -i, --tun-ip ADDR TUN interface IP address (e.g., 10.0.0.1/24)\n");
printf(" -d, --debug CONFIG Debug configuration (e.g., \"etcp:debug,routing:info\")\n");
printf(" -f, --foreground Run in foreground (don't daemonize)\n");
printf(" -h, --help Show this help\n");
printf("\nExamples:\n");
printf(" %s -c myconfig.conf -t tun0 -i 10.0.0.1/24\n", progname);
printf(" %s -c myconfig.conf\n", progname);
printf(" %s --config server.conf --pidfile /var/run/utun.pid\n", progname);
}
@ -249,6 +245,15 @@ int main(int argc, char *argv[]) {
return 0;
}
// Initialize global debug system early if configured from command line
if (args.debug_config) {
debug_config_init();
if (debug_parse_config(args.debug_config) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid debug configuration: %s", args.debug_config);
return 1;
}
}
// Create uasync instance
struct UASYNC* ua = uasync_create();
main_ua=ua;
@ -260,7 +265,7 @@ int main(int argc, char *argv[]) {
// Create and initialize instance
struct UTUN_INSTANCE *instance = utun_instance_create(ua, args.config_file, args.log_file);
if (!instance) {
fprintf(stderr, "Failed to create UTUN instance\n");
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to create UTUN instance");
return 1;
}
@ -271,14 +276,14 @@ int main(int argc, char *argv[]) {
// Initialize all components (TUN, routing, connections)
if (utun_instance_init(instance) < 0) {
fprintf(stderr, "Failed to initialize instance\n");
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to initialize instance");
utun_instance_destroy(instance);
return 1;
}
// Register sockets with uasync
if (utun_instance_register_sockets(instance) < 0) {
fprintf(stderr, "Failed to register sockets\n");
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to register sockets");
utun_instance_destroy(instance);
return 1;
}

85
src/utun_instance.c

@ -15,10 +15,13 @@
#include <unistd.h>
#include <arpa/inet.h>
// Forward declarations
static void tun_read_callback(int fd, void* user_arg);
static uint32_t get_dest_ip(const uint8_t *packet, size_t len);
// Global instance for signal handlers
static struct UTUN_INSTANCE *g_instance = NULL;
@ -32,6 +35,10 @@ struct UTUN_INSTANCE* utun_instance_create(struct UASYNC* ua, const char *config
instance->log_fp = NULL;
instance->ua = ua;
// Note: Global debug system is initialized from command line arguments if provided
// If not initialized via command line, instance-specific logging can be set up here
// The first initialization wins - either global (from main) or instance-specific
// Ensure keys and node_id exist in config
if (config_ensure_keys_and_node_id(config_file) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to ensure keys and node_id in config: %s", config_file);
@ -47,8 +54,8 @@ struct UTUN_INSTANCE* utun_instance_create(struct UASYNC* ua, const char *config
return NULL;
}
// Open log file
if (log_file) {
// Open log file only if not using global debug system output
if (log_file && !g_debug_config.output_file) {
instance->log_fp = fopen(log_file, "a");
if (!instance->log_fp) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to open log file %s: %s", log_file, strerror(errno));
@ -64,41 +71,67 @@ struct UTUN_INSTANCE* utun_instance_create(struct UASYNC* ua, const char *config
instance->pkt_pool=memory_pool_init(PACKET_DATA_SIZE+100);
/*
// Initialize TUN device
if (tun_create(&instance->tun) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to create TUN device");
return -1;
free(instance);
return NULL;
}
// Configure TUN device
if (tun_set_ip(instance->tun.ifname, instance->config->global.tun_ip) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to set TUN IP");
tun_close(&instance->tun);
return -1;
// Configure TUN device IP (BSD: use config IP as-is, Linux/Win: IP/32)
char tun_ip_str[64];
char ip_buffer[64];
// Convert struct IP to string using inet_ntop
if (instance->config->global.tun_ip.family == AF_INET) {
inet_ntop(AF_INET, &instance->config->global.tun_ip.addr.v4, ip_buffer, sizeof(ip_buffer));
} else {
inet_ntop(AF_INET6, &instance->config->global.tun_ip.addr.v6, ip_buffer, sizeof(ip_buffer));
}
if (instance->config->global.mtu > 0) {
if (tun_set_mtu(instance->tun.ifname, instance->config->global.mtu) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to set TUN MTU");
#ifdef __linux__
snprintf(tun_ip_str, sizeof(tun_ip_str), "%s/32", ip_buffer);
#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
// BSD systems: use config IP as-is, peer IP will be 192.0.2.1
snprintf(tun_ip_str, sizeof(tun_ip_str), "%s", ip_buffer);
#else
snprintf(tun_ip_str, sizeof(tun_ip_str), "%s/32", ip_buffer); // Default to /32
#endif
if (tun_set_ip(instance->tun.ifname, tun_ip_str) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to set TUN IP: %s", tun_ip_str);
tun_close(&instance->tun);
return -1;
free(instance);
return NULL;
}
// Set MTU (default 1500)
int mtu = instance->config->global.mtu > 0 ? instance->config->global.mtu : 1500;
if (tun_set_mtu(instance->tun.ifname, mtu) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to set TUN MTU: %d", mtu);
tun_close(&instance->tun);
free(instance);
return NULL;
}
if (tun_set_up(instance->tun.ifname) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to bring up TUN interface");
tun_close(&instance->tun);
return -1;
free(instance);
return NULL;
}
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN interface initialized: %s with IP %s",
instance->tun.ifname, tun_ip_str);
// Create routing table
instance->routing_table = routing_table_create();
if (!instance->routing_table) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "Failed to create routing table");
return -1;
tun_close(&instance->tun);
free(instance);
return NULL;
}
*/
// Initialize connections from configuration - moved to utun_instance_init
// to avoid double initialization
/*
@ -116,7 +149,7 @@ struct UTUN_INSTANCE* utun_instance_create(struct UASYNC* ua, const char *config
void utun_instance_destroy(struct UTUN_INSTANCE *instance) {
if (!instance) return;
printf("[INSTANCE_DESTROY] Starting cleanup for instance %p\n", instance);
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Starting cleanup for instance %p", instance);
// Диагностика ресурсов ДО cleanup
utun_instance_diagnose_leaks(instance, "BEFORE_CLEANUP");
@ -161,7 +194,7 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) {
// Cleanup TUN
if (instance->tun.fd >= 0) {
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Closing TUN interface");
DEBUG_INFO(DEBUG_CATEGORY_TUN, "Closing TUN interface: %s", instance->tun.ifname);
tun_close(&instance->tun);
}
@ -220,6 +253,18 @@ void utun_instance_stop(struct UTUN_INSTANCE *instance) {
int utun_instance_init(struct UTUN_INSTANCE *instance) {
if (!instance) return -1;
// Register TUN socket with uasync
if (instance->tun.fd >= 0) {
instance->tun_socket_id = uasync_add_socket(instance->ua, instance->tun.fd,
tun_read_callback, NULL, NULL, instance);
if (!instance->tun_socket_id) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to register TUN socket with uasync");
return -1;
}
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN interface registered: %s (fd=%d)",
instance->tun.ifname, instance->tun.fd);
}
// Initialize connections
if (init_connections(instance) < 0) {
return -1;
@ -231,7 +276,7 @@ int utun_instance_init(struct UTUN_INSTANCE *instance) {
// Диагностическая функция для анализа утечек
void utun_instance_diagnose_leaks(struct UTUN_INSTANCE *instance, const char *phase) {
if (!instance) {
printf("[DIAGNOSE] NULL instance for phase: %s\n", phase);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "[DIAGNOSE] NULL instance for phase: %s", phase);
return;
}

2
test_client.conf

@ -2,7 +2,7 @@
my_node_id=0x2222222222222222
my_private_key=2313912e5d34768983b0e06530a48c77816d228a5b5605e1ab3dc443d107a3dc
my_public_key=ede6cec8a9023339a758f60883ef41534d24a1ffdc09bbb787a5c24ddfd891e3092461835a97d37944c681fc6b2c1f5acde8ad192f7d2cdc9920aa0d3ff78e99
tun_ip=10.99.0.2/24
tun_ip=10.99.0.2
tun_ifname=tun98
[server: test]

2
test_server.conf

@ -2,7 +2,7 @@
my_node_id=0x1111111111111111
my_private_key=1313912e5d34768983b0e06530a48c77816d228a5b5605e1ab3dc443d107a3dc
my_public_key=dde6cec8a9023339a758f60883ef41534d24a1ffdc09bbb787a5c24ddfd891e3092461835a97d37944c681fc6b2c1f5acde8ad192f7d2cdc9920aa0d3ff78e99
tun_ip=10.99.0.1/24
tun_ip=10.99.0.1
tun_ifname=tun99
[server: test]

16
tests/Makefile.am

@ -3,7 +3,9 @@
# Essential working tests
check_PROGRAMS = test_etcp_crypto$(EXEEXT) \
test_crypto$(EXEEXT) \
test_etcp_two_instances$(EXEEXT)
test_etcp_two_instances$(EXEEXT) \
test_ll_queue_pos$(EXEEXT) \
test_ll_queue_unified$(EXEEXT)
# Basic includes
AM_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
@ -21,7 +23,17 @@ test_crypto_LDADD = $(top_builddir)/tinycrypt/lib/source/utun-aes_encrypt.o $(to
# ETCP two instances test - comprehensive connection test
test_etcp_two_instances_SOURCES = test_etcp_two_instances.c
test_etcp_two_instances_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_etcp_two_instances_LDADD = $(top_builddir)/src/utun-config_parser.o $(top_builddir)/src/utun-config_updater.o $(top_builddir)/src/utun-crc32.o $(top_builddir)/src/utun-etcp.o $(top_builddir)/src/utun-etcp_connections.o $(top_builddir)/src/utun-secure_channel.o $(top_builddir)/src/utun-routing.o $(top_builddir)/src/utun-tun_if.o $(top_builddir)/src/utun-utun_instance.o $(top_builddir)/tinycrypt/lib/source/utun-aes_encrypt.o $(top_builddir)/tinycrypt/lib/source/utun-aes_decrypt.o $(top_builddir)/tinycrypt/lib/source/utun-ccm_mode.o $(top_builddir)/tinycrypt/lib/source/utun-cmac_mode.o $(top_builddir)/tinycrypt/lib/source/utun-ctr_mode.o $(top_builddir)/tinycrypt/lib/source/utun-ecc.o $(top_builddir)/tinycrypt/lib/source/utun-ecc_dh.o $(top_builddir)/tinycrypt/lib/source/utun-ecc_dsa.o $(top_builddir)/tinycrypt/lib/source/utun-sha256.o $(top_builddir)/tinycrypt/lib/source/utun-ecc_platform_specific.o $(top_builddir)/tinycrypt/lib/source/utun-utils.o $(top_builddir)/lib/libuasync.a -lpthread -lcrypto
test_etcp_two_instances_LDADD = $(top_builddir)/src/utun-config_parser.o $(top_builddir)/src/utun-config_updater.o $(top_builddir)/src/utun-crc32.o $(top_builddir)/src/utun-etcp.o $(top_builddir)/src/utun-etcp_connections.o $(top_builddir)/src/utun-etcp_loadbalancer.o $(top_builddir)/src/utun-secure_channel.o $(top_builddir)/src/utun-routing.o $(top_builddir)/src/utun-tun_if.o $(top_builddir)/src/utun-utun_instance.o $(top_builddir)/tinycrypt/lib/source/utun-aes_encrypt.o $(top_builddir)/tinycrypt/lib/source/utun-aes_decrypt.o $(top_builddir)/tinycrypt/lib/source/utun-ccm_mode.o $(top_builddir)/tinycrypt/lib/source/utun-cmac_mode.o $(top_builddir)/tinycrypt/lib/source/utun-ctr_mode.o $(top_builddir)/tinycrypt/lib/source/utun-ecc.o $(top_builddir)/tinycrypt/lib/source/utun-ecc_dh.o $(top_builddir)/tinycrypt/lib/source/utun-ecc_dsa.o $(top_builddir)/tinycrypt/lib/source/utun-sha256.o $(top_builddir)/tinycrypt/lib/source/utun-ecc_platform_specific.o $(top_builddir)/tinycrypt/lib/source/utun-utils.o $(top_builddir)/lib/libuasync.a -lpthread -lcrypto
# LL queue position test
test_ll_queue_pos_SOURCES = test_ll_queue_pos.c
test_ll_queue_pos_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_ll_queue_pos_LDADD = $(top_builddir)/lib/libuasync.a -lpthread -lcrypto
# Unified comprehensive ll_queue test
test_ll_queue_unified_SOURCES = test_ll_queue_unified.c
test_ll_queue_unified_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_ll_queue_unified_LDADD = $(top_builddir)/lib/libuasync.a -lpthread -lcrypto
# Register tests
TESTS = $(check_PROGRAMS)

BIN
tests/test_crypto

Binary file not shown.

BIN
tests/test_etcp_crypto

Binary file not shown.

BIN
tests/test_etcp_two_instances

Binary file not shown.

407
tests/test_ll_queue_pos.c

@ -0,0 +1,407 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <assert.h>
#include "../lib/ll_queue.h"
#include "../lib/u_async.h"
// Тестовые данные
struct test_data {
int value;
char name[32];
};
// Простой контекст uasync для тестов
struct test_context {
struct UASYNC* ua;
int callback_count;
};
// Коллбэк для тестов
void test_callback(struct ll_queue* q, struct ll_entry* entry, void* arg) {
struct test_context* ctx = (struct test_context*)arg;
ctx->callback_count++;
printf("Callback called for entry %p\n", entry);
}
// Базовый тест двусвязного списка
void test_doubly_linked_basics() {
printf("=== Тест базовых операций двусвязного списка ===\n");
struct UASYNC* ua = uasync_create();
struct ll_queue* q = queue_new(ua, NULL);
assert(q != NULL);
assert(queue_entry_count(q) == 0);
// Создать и добавить несколько элементов
for (int i = 0; i < 5; i++) {
struct ll_entry* entry = queue_entry_new(sizeof(struct test_data));
assert(entry != NULL);
struct test_data* data = (struct test_data*)ll_entry_data(entry);
data->value = i;
snprintf(data->name, sizeof(data->name), "item_%d", i);
assert(queue_entry_put(q, entry) == 0);
}
assert(queue_entry_count(q) == 5);
// Проверить, что prev/next указатели корректны
struct ll_entry* curr = q->head;
int count = 0;
while (curr) {
struct test_data* data = (struct test_data*)ll_entry_data(curr);
printf(" Forward: %s (value=%d)\n", data->name, data->value);
// Проверить двусвязность
if (curr->prev) {
struct test_data* prev_data = (struct test_data*)ll_entry_data(curr->prev);
printf(" prev: %s\n", prev_data->name);
}
if (curr->next) {
struct test_data* next_data = (struct test_data*)ll_entry_data(curr->next);
printf(" next: %s\n", next_data->name);
}
curr = curr->next;
count++;
}
assert(count == 5);
// Проверить обратный проход
curr = q->tail;
count = 0;
while (curr) {
struct test_data* data = (struct test_data*)ll_entry_data(curr);
printf(" Backward: %s (value=%d)\n", data->name, data->value);
curr = curr->prev;
count++;
}
assert(count == 5);
queue_free(q);
uasync_destroy(ua);
printf("✓ Базовые операции двусвязного списка работают\n\n");
}
// Тест навигации по позициям
void test_position_navigation() {
printf("=== Тест навигации по позициям ===\n");
struct UASYNC* ua = uasync_create();
struct ll_queue* q = queue_new(ua, NULL);
// Добавить тестовые данные
for (int i = 0; i < 5; i++) {
struct ll_entry* entry = queue_entry_new(sizeof(struct test_data));
struct test_data* data = (struct test_data*)ll_entry_data(entry);
data->value = i;
snprintf(data->name, sizeof(data->name), "item_%d", i);
queue_entry_put(q, entry);
}
struct ll_queue_pos* pos = queue_pos_new(q);
assert(pos != NULL);
// Тест reset
queue_pos_reset(pos);
assert(queue_pos_index(pos) == -1);
assert(queue_pos_current(pos) == NULL);
// Тест next
int count = 0;
while (queue_pos_next(pos)) {
struct ll_entry* entry = queue_pos_current(pos);
struct test_data* data = (struct test_data*)ll_entry_data(entry);
printf(" Position %d: %s (value=%d)\n", queue_pos_index(pos), data->name, data->value);
assert(queue_pos_index(pos) == count);
assert(data->value == count);
count++;
}
assert(count == 5);
// Тест prev
int prev_count = 0;
while (queue_pos_prev(pos)) {
struct ll_entry* entry = queue_pos_current(pos);
if (entry) { // Проверить, что entry не NULL
struct test_data* data = (struct test_data*)ll_entry_data(entry);
printf(" Back Position %d: %s (value=%d)\n", queue_pos_index(pos), data->name, data->value);
} else {
printf(" Back Position %d: NULL (before head)\n", queue_pos_index(pos));
}
prev_count++;
}
assert(prev_count == 5);
// Тест seek
assert(queue_pos_seek(pos, 2) == 1);
assert(queue_pos_index(pos) == 2);
struct ll_entry* entry = queue_pos_current(pos);
struct test_data* data = (struct test_data*)ll_entry_data(entry);
assert(data->value == 2);
printf(" Seek to 2: %s (value=%d)\n", data->name, data->value);
queue_pos_free(pos);
queue_free(q);
uasync_destroy(ua);
printf("✓ Навигация по позициям работает\n\n");
}
// Тест вставки и удаления
void test_insert_remove_operations() {
printf("=== Тест вставки и удаления ===\n");
struct UASYNC* ua = uasync_create();
struct ll_queue* q = queue_new(ua, NULL);
// Создать начальную очередь: [0, 1, 2, 3, 4]
for (int i = 0; i < 5; i++) {
struct ll_entry* entry = queue_entry_new(sizeof(struct test_data));
struct test_data* data = (struct test_data*)ll_entry_data(entry);
data->value = i;
snprintf(data->name, sizeof(data->name), "item_%d", i);
queue_entry_put(q, entry);
}
struct ll_queue_pos* pos = queue_pos_new(q);
// Тест вставки перед элементом 2
queue_pos_seek(pos, 2);
struct ll_entry* new_entry = queue_entry_new(sizeof(struct test_data));
struct test_data* new_data = (struct test_data*)ll_entry_data(new_entry);
new_data->value = 99;
snprintf(new_data->name, sizeof(new_data->name), "inserted_before_2");
assert(queue_pos_insert(pos, new_entry, 0) == 0); // Вставить перед
assert(queue_entry_count(q) == 6);
// Проверить вставку
queue_pos_seek(pos, 2);
struct ll_entry* check_entry = queue_pos_current(pos);
struct test_data* check_data = (struct test_data*)ll_entry_data(check_entry);
assert(check_data->value == 99);
printf(" Inserted before position 2: %s\n", check_data->name);
// Тест вставки после элемента 2 (теперь это вставленный элемент)
new_entry = queue_entry_new(sizeof(struct test_data));
new_data = (struct test_data*)ll_entry_data(new_entry);
new_data->value = 88;
snprintf(new_data->name, sizeof(new_data->name), "inserted_after_2");
assert(queue_pos_insert(pos, new_entry, 1) == 0); // Вставить после
assert(queue_entry_count(q) == 7);
// Проверить вставку
queue_pos_next(pos);
check_entry = queue_pos_current(pos);
check_data = (struct test_data*)ll_entry_data(check_entry);
assert(check_data->value == 88);
printf(" Inserted after position 2: %s\n", check_data->name);
// Тест удаления
queue_pos_seek(pos, 3); // Вернемся к вставленному элементу
struct ll_entry* removed_entry = queue_pos_remove(pos);
assert(removed_entry != NULL);
struct test_data* removed_data = (struct test_data*)ll_entry_data(removed_entry);
assert(removed_data->value == 88);
printf(" Removed element: %s\n", removed_data->name);
assert(queue_entry_count(q) == 6);
queue_entry_free(removed_entry);
// Тест удаления по указателю
// Найдем элемент с value = 99 и удалим его
struct ll_entry* curr = q->head;
while (curr) {
struct test_data* data = (struct test_data*)ll_entry_data(curr);
if (data->value == 99) {
assert(queue_entry_remove(q, curr) == 0);
queue_entry_free(curr);
break;
}
curr = curr->next;
}
assert(queue_entry_count(q) == 5);
printf(" Removed element by pointer: item_99\n");
queue_pos_free(pos);
queue_free(q);
uasync_destroy(ua);
printf("✓ Вставка и удаление работают\n\n");
}
// Стресс-тест
void test_stress() {
printf("=== Стресс-тест ===\n");
struct UASYNC* ua = uasync_create();
struct ll_queue* q = queue_new(ua, NULL);
const int iterations = 1000;
const int max_queue_size = 50;
srand(time(NULL));
for (int i = 0; i < iterations; i++) {
int operation = rand() % 4;
switch (operation) {
case 0: { // Добавить в конец
struct ll_entry* entry = queue_entry_new(sizeof(int));
int* value = (int*)ll_entry_data(entry);
*value = rand();
queue_entry_put(q, entry);
break;
}
case 1: { // Добавить в начало
struct ll_entry* entry = queue_entry_new(sizeof(int));
int* value = (int*)ll_entry_data(entry);
*value = rand();
queue_entry_put_first(q, entry);
break;
}
case 2: { // Удалить из начала
struct ll_entry* entry = queue_entry_get(q);
if (entry) {
queue_entry_free(entry);
}
break;
}
case 3: { // Произвольная операция с позицией
if (queue_entry_count(q) > 0) {
struct ll_queue_pos* pos = queue_pos_new(q);
int index = rand() % queue_entry_count(q);
if (queue_pos_seek(pos, index)) {
// Случайная операция: вставка или удаление
if (rand() % 2) {
struct ll_entry* entry = queue_entry_new(sizeof(int));
int* value = (int*)ll_entry_data(entry);
*value = rand();
queue_pos_insert(pos, entry, rand() % 2);
} else {
struct ll_entry* removed = queue_pos_remove(pos);
if (removed) {
queue_entry_free(removed);
}
}
}
queue_pos_free(pos);
}
break;
}
}
// Проверить размер очереди
if (queue_entry_count(q) > max_queue_size) {
// Удалить лишние элементы
while (queue_entry_count(q) > max_queue_size / 2) {
struct ll_entry* entry = queue_entry_get(q);
if (entry) {
queue_entry_free(entry);
}
}
}
// Периодическая проверка целостности
if (i % 100 == 0) {
int count = 0;
struct ll_entry* curr = q->head;
while (curr) {
count++;
// Проверить двусвязность
if (curr->prev) {
assert(curr->prev->next == curr);
}
if (curr->next) {
assert(curr->next->prev == curr);
}
curr = curr->next;
}
assert(count == queue_entry_count(q));
}
}
printf(" Прошло %d случайных операций\n", iterations);
printf(" Финальный размер очереди: %d\n", queue_entry_count(q));
queue_free(q);
uasync_destroy(ua);
printf("✓ Стресс-тест пройден\n\n");
}
// Тест совместимости существующего API
void test_api_compatibility() {
printf("=== Тест совместимости API ===\n");
struct UASYNC* ua = uasync_create();
struct ll_queue* q = queue_new(ua, NULL);
// Тест старого API
struct ll_entry* entries[5];
for (int i = 0; i < 5; i++) {
entries[i] = queue_entry_new(sizeof(struct test_data));
struct test_data* data = (struct test_data*)ll_entry_data(entries[i]);
data->value = i;
snprintf(data->name, sizeof(data->name), "compat_%d", i);
}
// Добавить в конец
for (int i = 0; i < 5; i++) {
assert(queue_entry_put(q, entries[i]) == 0);
}
assert(queue_entry_count(q) == 5);
// Извлечь из начала
for (int i = 0; i < 5; i++) {
struct ll_entry* entry = queue_entry_get(q);
assert(entry == entries[i]); // Проверить порядок FIFO
struct test_data* data = (struct test_data*)ll_entry_data(entry);
printf(" Extracted: %s (value=%d)\n", data->name, data->value);
assert(data->value == i);
queue_entry_free(entry);
}
assert(queue_entry_count(q) == 0);
// Тест put_first
for (int i = 0; i < 3; i++) {
struct ll_entry* entry = queue_entry_new(sizeof(int));
int* value = (int*)ll_entry_data(entry);
*value = i;
queue_entry_put_first(q, entry);
}
// Извлечь в обратном порядке (LIFO)
for (int i = 0; i < 3; i++) {
struct ll_entry* entry = queue_entry_get(q);
int* value = (int*)ll_entry_data(entry);
printf(" LIFO extracted: %d (expected %d)\n", *value, 2-i);
assert(*value == 2-i);
queue_entry_free(entry);
}
queue_free(q);
uasync_destroy(ua);
printf("✓ Совместимость API сохранена\n\n");
}
int main() {
printf("=== Запуск тестов doubly-linked ll_queue ===\n\n");
test_doubly_linked_basics();
test_position_navigation();
test_insert_remove_operations();
test_stress();
test_api_compatibility();
printf("=== Все тесты пройдены успешно! ===\n");
return 0;
}

752
tests/test_ll_queue_unified.c

@ -0,0 +1,752 @@
/**
* Unified comprehensive test suite for ll_queue module
* Combines: basic operations, position navigation, doubly-linked list, stress testing
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <assert.h>
#include <unistd.h>
#include <sys/time.h>
#include "../lib/ll_queue.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
/* Test statistics */
static struct {
int tests_run;
int tests_passed;
int tests_failed;
/* Performance metrics */
double total_time_ms;
int operations_count;
/* Error tracking */
int memory_errors;
int invalid_parameter_errors;
} test_stats = {0};
/* Test result tracking */
#define TEST_START(name) do { \
printf("TEST: %s... ", name); \
test_stats.tests_run++; \
} while(0)
#define TEST_PASS() do { \
printf("PASS\n"); \
test_stats.tests_passed++; \
} while(0)
#define TEST_FAIL(msg) do { \
printf("FAIL: %s\n", msg); \
test_stats.tests_failed++; \
} while(0)
#define ASSERT_TRUE(cond, msg) do { \
if (!(cond)) { \
TEST_FAIL(msg); \
return; \
} \
} while(0)
#define ASSERT_FALSE(cond, msg) ASSERT_TRUE(!(cond), msg)
#define ASSERT_NULL(ptr, msg) ASSERT_TRUE((ptr) == NULL, msg)
#define ASSERT_NOT_NULL(ptr, msg) ASSERT_TRUE((ptr) != NULL, msg)
#define ASSERT_EQ(a, b, msg) ASSERT_TRUE((a) == (b), msg)
#define ASSERT_NEQ(a, b, msg) ASSERT_TRUE((a) != (b), msg)
/* Test data structure */
struct test_data {
int value;
char name[32];
int checksum;
};
/* Utility functions */
static double get_time_ms() {
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0;
}
static struct test_data* create_test_data(int value) {
struct ll_entry* entry = queue_entry_new(sizeof(struct test_data));
if (!entry) return NULL;
struct test_data* data = (struct test_data*)ll_entry_data(entry);
data->value = value;
snprintf(data->name, sizeof(data->name), "item_%d", value);
data->checksum = value * 7 + 3; // Simple checksum
return data;
}
static int verify_test_data(struct test_data* data) {
if (!data) return 0;
return data->checksum == (data->value * 7 + 3);
}
/* Test: Basic doubly-linked list operations */
static void test_doubly_linked_basics() {
TEST_START("doubly_linked_basics");
struct UASYNC* ua = uasync_create();
ASSERT_NOT_NULL(ua, "Failed to create uasync");
struct ll_queue* q = queue_new(ua, NULL);
ASSERT_NOT_NULL(q, "Failed to create queue");
ASSERT_EQ(queue_entry_count(q), 0, "Queue should be empty");
// Create and add test elements
for (int i = 0; i < 5; i++) {
struct ll_entry* entry = queue_entry_new(sizeof(struct test_data));
ASSERT_NOT_NULL(entry, "Failed to create entry");
struct test_data* data = (struct test_data*)ll_entry_data(entry);
data->value = i;
snprintf(data->name, sizeof(data->name), "item_%d", i);
data->checksum = i * 7 + 3;
ASSERT_EQ(queue_entry_put(q, entry), 0, "Failed to add entry to queue");
}
ASSERT_EQ(queue_entry_count(q), 5, "Wrong queue count");
// Verify forward traversal with doubly-linked integrity
struct ll_entry* curr = q->head;
int forward_count = 0;
while (curr) {
struct test_data* data = (struct test_data*)ll_entry_data(curr);
ASSERT_TRUE(verify_test_data(data), "Data corruption in forward traversal");
ASSERT_EQ(data->value, forward_count, "Wrong value in forward traversal");
// Verify doubly-linked integrity
if (curr->prev) {
ASSERT_NOT_NULL(curr->prev->next, "Broken prev link");
ASSERT_EQ(curr->prev->next, curr, "Invalid prev->next link");
}
if (curr->next) {
ASSERT_NOT_NULL(curr->next->prev, "Broken next link");
ASSERT_EQ(curr->next->prev, curr, "Invalid next->prev link");
}
curr = curr->next;
forward_count++;
}
ASSERT_EQ(forward_count, 5, "Wrong forward count");
// Verify backward traversal
curr = q->tail;
int backward_count = 4;
while (curr) {
struct test_data* data = (struct test_data*)ll_entry_data(curr);
ASSERT_TRUE(verify_test_data(data), "Data corruption in backward traversal");
ASSERT_EQ(data->value, backward_count, "Wrong value in backward traversal");
curr = curr->prev;
backward_count--;
}
ASSERT_EQ(backward_count, -1, "Wrong backward count");
queue_free(q);
uasync_destroy(ua);
TEST_PASS();
}
/* Test: Position navigation system */
static void test_position_navigation() {
TEST_START("position_navigation");
struct UASYNC* ua = uasync_create();
ASSERT_NOT_NULL(ua, "Failed to create uasync");
struct ll_queue* q = queue_new(ua, NULL);
ASSERT_NOT_NULL(q, "Failed to create queue");
// Add test elements
for (int i = 0; i < 5; i++) {
struct ll_entry* entry = queue_entry_new(sizeof(struct test_data));
ASSERT_NOT_NULL(entry, "Failed to create entry");
struct test_data* data = (struct test_data*)ll_entry_data(entry);
data->value = i;
snprintf(data->name, sizeof(data->name), "item_%d", i);
data->checksum = i * 7 + 3;
ASSERT_EQ(queue_entry_put(q, entry), 0, "Failed to add entry");
}
struct ll_queue_pos* pos = queue_pos_new(q);
ASSERT_NOT_NULL(pos, "Failed to create position");
// Test initial state
ASSERT_EQ(queue_pos_index(pos), -1, "Wrong initial index");
ASSERT_NULL(queue_pos_current(pos), "Current should be NULL initially");
// Test forward navigation
int count = 0;
while (queue_pos_next(pos)) {
struct ll_entry* entry = queue_pos_current(pos);
ASSERT_NOT_NULL(entry, "Current entry should not be NULL");
ASSERT_EQ(queue_pos_index(pos), count, "Wrong index in forward navigation");
struct test_data* data = (struct test_data*)ll_entry_data(entry);
ASSERT_TRUE(verify_test_data(data), "Data corruption in position navigation");
ASSERT_EQ(data->value, count, "Wrong value in forward navigation");
count++;
}
ASSERT_EQ(count, 5, "Wrong forward navigation count");
// Test backward navigation
int prev_count = 0;
while (queue_pos_prev(pos)) {
struct ll_entry* entry = queue_pos_current(pos);
// Note: entry can be NULL when at position -1 (before head)
if (entry) {
struct test_data* data = (struct test_data*)ll_entry_data(entry);
ASSERT_TRUE(verify_test_data(data), "Data corruption in backward navigation");
}
prev_count++;
}
ASSERT_EQ(prev_count, 5, "Wrong backward navigation count");
// Test seek functionality
ASSERT_EQ(queue_pos_seek(pos, 2), 1, "Seek to index 2 failed");
ASSERT_EQ(queue_pos_index(pos), 2, "Wrong index after seek");
struct ll_entry* entry = queue_pos_current(pos);
ASSERT_NOT_NULL(entry, "Current entry should not be NULL after seek");
struct test_data* data = (struct test_data*)ll_entry_data(entry);
ASSERT_TRUE(verify_test_data(data), "Data corruption after seek");
ASSERT_EQ(data->value, 2, "Wrong value after seek");
// Test invalid seek
ASSERT_EQ(queue_pos_seek(pos, -2), 0, "Invalid seek should fail");
ASSERT_EQ(queue_pos_seek(pos, 10), 0, "Out of bounds seek should fail");
queue_pos_free(pos);
queue_free(q);
uasync_destroy(ua);
TEST_PASS();
}
/* Test: Position-based insert and remove operations */
static void test_position_insert_remove() {
TEST_START("position_insert_remove");
struct UASYNC* ua = uasync_create();
ASSERT_NOT_NULL(ua, "Failed to create uasync");
struct ll_queue* q = queue_new(ua, NULL);
ASSERT_NOT_NULL(q, "Failed to create queue");
// Create initial queue: [0, 1, 2, 3, 4]
for (int i = 0; i < 5; i++) {
struct ll_entry* entry = queue_entry_new(sizeof(struct test_data));
ASSERT_NOT_NULL(entry, "Failed to create entry");
struct test_data* data = (struct test_data*)ll_entry_data(entry);
data->value = i;
snprintf(data->name, sizeof(data->name), "item_%d", i);
data->checksum = i * 7 + 3;
ASSERT_EQ(queue_entry_put(q, entry), 0, "Failed to add entry");
}
struct ll_queue_pos* pos = queue_pos_new(q);
ASSERT_NOT_NULL(pos, "Failed to create position");
// Test insertion before position 2
ASSERT_EQ(queue_pos_seek(pos, 2), 1, "Failed to seek to position 2");
struct ll_entry* new_entry = queue_entry_new(sizeof(struct test_data));
ASSERT_NOT_NULL(new_entry, "Failed to create new entry");
struct test_data* new_data = (struct test_data*)ll_entry_data(new_entry);
new_data->value = 99;
snprintf(new_data->name, sizeof(new_data->name), "inserted_before_2");
new_data->checksum = 99 * 7 + 3;
ASSERT_EQ(queue_pos_insert(pos, new_entry, 0), 0, "Failed to insert before position");
ASSERT_EQ(queue_entry_count(q), 6, "Wrong count after insertion");
// Verify insertion (refresh position first due to version change)
queue_pos_refresh(pos); // Обновить позицию после изменения очереди
ASSERT_EQ(queue_pos_seek(pos, 2), 1, "Failed to seek after insertion");
struct ll_entry* check_entry = queue_pos_current(pos);
ASSERT_NOT_NULL(check_entry, "Current entry should not be NULL after insertion");
struct test_data* check_data = (struct test_data*)ll_entry_data(check_entry);
ASSERT_TRUE(verify_test_data(check_data), "Data corruption after insertion");
ASSERT_EQ(check_data->value, 99, "Wrong value after insertion");
// Test insertion after position (refresh position first, then create new entry)
queue_pos_refresh(pos); // Обновить позицию после предыдущей вставки
struct ll_entry* new_entry2 = queue_entry_new(sizeof(struct test_data));
ASSERT_NOT_NULL(new_entry2, "Failed to create second new entry");
struct test_data* new_data2 = (struct test_data*)ll_entry_data(new_entry2);
new_data2->value = 88;
snprintf(new_data2->name, sizeof(new_data2->name), "inserted_after_2");
new_data2->checksum = 88 * 7 + 3;
ASSERT_EQ(queue_pos_insert(pos, new_entry2, 1), 0, "Failed to insert after position");
ASSERT_EQ(queue_entry_count(q), 7, "Wrong count after second insertion");
// Test removal (refresh position first)
queue_pos_refresh(pos); // Обновить позицию после вставки
struct ll_entry* removed_entry = queue_pos_remove(pos);
ASSERT_NOT_NULL(removed_entry, "Failed to remove entry");
ASSERT_EQ(queue_entry_count(q), 6, "Wrong count after removal");
struct test_data* removed_data = (struct test_data*)ll_entry_data(removed_entry);
ASSERT_TRUE(verify_test_data(removed_data), "Data corruption in removed entry");
ASSERT_EQ(removed_data->value, 99, "Wrong value in removed entry");
queue_entry_free(removed_entry);
// Test removal by pointer (find element with value 88 - the other inserted element)
struct ll_entry* curr = q->head;
int found = 0;
while (curr) {
struct test_data* data = (struct test_data*)ll_entry_data(curr);
if (data->value == 88) { // Target the element that was inserted after position 2
ASSERT_EQ(queue_entry_remove(q, curr), 0, "Failed to remove by pointer");
queue_entry_free(curr);
found = 1;
break;
}
curr = curr->next;
}
ASSERT_TRUE(found, "Element with value 88 not found in queue after operations");
ASSERT_EQ(queue_entry_count(q), 5, "Wrong count after pointer removal");
queue_pos_free(pos);
queue_free(q);
uasync_destroy(ua);
TEST_PASS();
}
/* Test: Stress testing with random operations */
static void test_stress_random_operations() {
TEST_START("stress_random_operations");
double start_time = get_time_ms();
struct UASYNC* ua = uasync_create();
ASSERT_NOT_NULL(ua, "Failed to create uasync");
struct ll_queue* q = queue_new(ua, NULL);
ASSERT_NOT_NULL(q, "Failed to create queue");
const int iterations = 1000;
const int max_queue_size = 50;
srand(time(NULL));
for (int i = 0; i < iterations; i++) {
int operation = rand() % 5; // 5 types of operations
switch (operation) {
case 0: { // Add to tail
struct ll_entry* entry = queue_entry_new(sizeof(struct test_data));
if (entry) {
struct test_data* data = (struct test_data*)ll_entry_data(entry);
data->value = rand() % 1000;
data->checksum = data->value * 7 + 3;
queue_entry_put(q, entry);
test_stats.operations_count++;
}
break;
}
case 1: { // Add to head
struct ll_entry* entry = queue_entry_new(sizeof(struct test_data));
if (entry) {
struct test_data* data = (struct test_data*)ll_entry_data(entry);
data->value = rand() % 1000;
data->checksum = data->value * 7 + 3;
queue_entry_put_first(q, entry);
test_stats.operations_count++;
}
break;
}
case 2: { // Remove from head
struct ll_entry* entry = queue_entry_get(q);
if (entry) {
struct test_data* data = (struct test_data*)ll_entry_data(entry);
if (!verify_test_data(data)) {
test_stats.memory_errors++;
}
queue_entry_free(entry);
test_stats.operations_count++;
}
break;
}
case 3: { // Position-based operations
if (queue_entry_count(q) > 0) {
struct ll_queue_pos* pos = queue_pos_new(q);
if (pos) {
int index = rand() % queue_entry_count(q);
if (queue_pos_seek(pos, index)) {
if (rand() % 2) {
// Insert operation
struct ll_entry* entry = queue_entry_new(sizeof(struct test_data));
if (entry) {
struct test_data* data = (struct test_data*)ll_entry_data(entry);
data->value = rand() % 1000;
data->checksum = data->value * 7 + 3;
queue_pos_insert(pos, entry, rand() % 2);
test_stats.operations_count++;
}
} else {
// Remove operation
struct ll_entry* removed = queue_pos_remove(pos);
if (removed) {
struct test_data* data = (struct test_data*)ll_entry_data(removed);
if (!verify_test_data(data)) {
test_stats.memory_errors++;
}
queue_entry_free(removed);
test_stats.operations_count++;
}
}
}
queue_pos_free(pos);
}
}
break;
}
case 4: { // Random remove by pointer
if (queue_entry_count(q) > 0) {
// Find random element to remove
int target_index = rand() % queue_entry_count(q);
struct ll_entry* curr = q->head;
for (int j = 0; j < target_index && curr; j++) {
curr = curr->next;
}
if (curr) {
struct test_data* data = (struct test_data*)ll_entry_data(curr);
if (verify_test_data(data)) {
queue_entry_remove(q, curr);
queue_entry_free(curr);
test_stats.operations_count++;
}
}
}
break;
}
}
// Keep queue size reasonable
while (queue_entry_count(q) > max_queue_size) {
struct ll_entry* entry = queue_entry_get(q);
if (entry) {
struct test_data* data = (struct test_data*)ll_entry_data(entry);
if (!verify_test_data(data)) {
test_stats.memory_errors++;
}
queue_entry_free(entry);
}
}
// Periodic integrity check
if (i % 100 == 0) {
int count = 0;
struct ll_entry* curr = q->head;
while (curr) {
count++;
// Verify doubly-linked integrity
if (curr->prev) {
if (curr->prev->next != curr) {
test_stats.memory_errors++;
}
}
if (curr->next) {
if (curr->next->prev != curr) {
test_stats.memory_errors++;
}
}
curr = curr->next;
}
if (count != queue_entry_count(q)) {
test_stats.memory_errors++;
}
}
}
double end_time = get_time_ms();
test_stats.total_time_ms += (end_time - start_time);
ASSERT_EQ(test_stats.memory_errors, 0, "Memory corruption detected during stress test");
ASSERT_TRUE(test_stats.operations_count > iterations / 2, "Too few operations performed");
queue_free(q);
uasync_destroy(ua);
TEST_PASS();
}
/* Test: API compatibility with existing code */
static void test_api_compatibility() {
TEST_START("api_compatibility");
struct UASYNC* ua = uasync_create();
ASSERT_NOT_NULL(ua, "Failed to create uasync");
struct ll_queue* q = queue_new(ua, NULL);
ASSERT_NOT_NULL(q, "Failed to create queue");
// Test FIFO operations (existing API)
struct ll_entry* entries[5];
for (int i = 0; i < 5; i++) {
entries[i] = queue_entry_new(sizeof(struct test_data));
ASSERT_NOT_NULL(entries[i], "Failed to create entry");
struct test_data* data = (struct test_data*)ll_entry_data(entries[i]);
data->value = i;
snprintf(data->name, sizeof(data->name), "fifo_%d", i);
data->checksum = i * 7 + 3;
}
// Add to tail (FIFO)
for (int i = 0; i < 5; i++) {
ASSERT_EQ(queue_entry_put(q, entries[i]), 0, "Failed to add entry (FIFO)");
}
ASSERT_EQ(queue_entry_count(q), 5, "Wrong count after FIFO adds");
// Extract from head (FIFO)
for (int i = 0; i < 5; i++) {
struct ll_entry* entry = queue_entry_get(q);
ASSERT_NOT_NULL(entry, "Failed to get entry (FIFO)");
ASSERT_EQ(entry, entries[i], "Wrong order in FIFO extraction");
struct test_data* data = (struct test_data*)ll_entry_data(entry);
ASSERT_TRUE(verify_test_data(data), "Data corruption in FIFO");
ASSERT_EQ(data->value, i, "Wrong value in FIFO extraction");
queue_entry_free(entry);
}
ASSERT_EQ(queue_entry_count(q), 0, "Queue should be empty after FIFO extraction");
// Test LIFO operations (existing API)
for (int i = 0; i < 3; i++) {
struct ll_entry* entry = queue_entry_new(sizeof(struct test_data));
ASSERT_NOT_NULL(entry, "Failed to create entry");
struct test_data* data = (struct test_data*)ll_entry_data(entry);
data->value = i;
data->checksum = i * 7 + 3;
ASSERT_EQ(queue_entry_put_first(q, entry), 0, "Failed to add entry (LIFO)");
}
ASSERT_EQ(queue_entry_count(q), 3, "Wrong count after LIFO adds");
// Extract from head (LIFO - should be reverse order)
for (int i = 0; i < 3; i++) {
struct ll_entry* entry = queue_entry_get(q);
ASSERT_NOT_NULL(entry, "Failed to get entry (LIFO)");
struct test_data* data = (struct test_data*)ll_entry_data(entry);
ASSERT_TRUE(verify_test_data(data), "Data corruption in LIFO");
ASSERT_EQ(data->value, 2 - i, "Wrong order in LIFO extraction");
queue_entry_free(entry);
}
ASSERT_EQ(queue_entry_count(q), 0, "Queue should be empty after LIFO extraction");
queue_free(q);
uasync_destroy(ua);
TEST_PASS();
}
/* Test: Performance benchmarks */
static void test_performance_benchmarks() {
TEST_START("performance_benchmarks");
struct UASYNC* ua = uasync_create();
ASSERT_NOT_NULL(ua, "Failed to create uasync");
struct ll_queue* q = queue_new(ua, NULL);
ASSERT_NOT_NULL(q, "Failed to create queue");
const int benchmark_size = 10000;
double start_time, end_time;
// Benchmark: Sequential add operations
start_time = get_time_ms();
for (int i = 0; i < benchmark_size; i++) {
struct ll_entry* entry = queue_entry_new(sizeof(struct test_data));
if (entry) {
struct test_data* data = (struct test_data*)ll_entry_data(entry);
data->value = i;
data->checksum = i * 7 + 3;
queue_entry_put(q, entry);
}
}
end_time = get_time_ms();
printf("\n Sequential add (%d ops): %.2f ms (%.2f ops/sec)",
benchmark_size, end_time - start_time,
benchmark_size * 1000.0 / (end_time - start_time));
// Benchmark: Sequential remove operations
start_time = get_time_ms();
for (int i = 0; i < benchmark_size; i++) {
struct ll_entry* entry = queue_entry_get(q);
if (entry) {
struct test_data* data = (struct test_data*)ll_entry_data(entry);
if (!verify_test_data(data)) {
test_stats.memory_errors++;
}
queue_entry_free(entry);
}
}
end_time = get_time_ms();
printf("\n Sequential remove (%d ops): %.2f ms (%.2f ops/sec)",
benchmark_size, end_time - start_time,
benchmark_size * 1000.0 / (end_time - start_time));
// Benchmark: Position navigation
// Rebuild queue for navigation benchmark
for (int i = 0; i < benchmark_size / 10; i++) {
struct ll_entry* entry = queue_entry_new(sizeof(struct test_data));
if (entry) {
queue_entry_put(q, entry);
}
}
struct ll_queue_pos* pos = queue_pos_new(q);
ASSERT_NOT_NULL(pos, "Failed to create position for benchmark");
start_time = get_time_ms();
int navigation_ops = 0;
for (int i = 0; i < benchmark_size / 10; i++) {
// Forward and backward navigation
while (queue_pos_next(pos)) navigation_ops++;
while (queue_pos_prev(pos)) navigation_ops++;
// Random seek operations
int target = rand() % queue_entry_count(q);
queue_pos_seek(pos, target);
navigation_ops++;
}
end_time = get_time_ms();
printf("\n Position navigation (%d ops): %.2f ms (%.2f ops/sec)",
navigation_ops, end_time - start_time,
navigation_ops * 1000.0 / (end_time - start_time));
queue_pos_free(pos);
queue_free(q);
uasync_destroy(ua);
TEST_PASS();
}
/* Test: Edge cases and error conditions */
static void test_edge_cases() {
TEST_START("edge_cases");
struct UASYNC* ua = uasync_create();
ASSERT_NOT_NULL(ua, "Failed to create uasync");
// Test NULL parameter handling
ASSERT_NULL(queue_pos_new(NULL), "Should return NULL for NULL queue");
ASSERT_NULL(queue_entry_get(NULL), "Should return NULL for NULL queue");
ASSERT_EQ(queue_entry_put(NULL, NULL), -1, "Should fail on NULL parameters");
struct ll_queue* q = queue_new(ua, NULL);
ASSERT_NOT_NULL(q, "Failed to create queue");
struct ll_queue_pos* pos = queue_pos_new(q);
ASSERT_NOT_NULL(pos, "Failed to create position");
// Test operations on empty queue
ASSERT_NULL(queue_entry_get(q), "Should return NULL from empty queue");
ASSERT_NULL(queue_pos_current(pos), "Should return NULL for empty queue");
ASSERT_EQ(queue_pos_seek(pos, 0), 0, "Seek should fail on empty queue");
// Test navigation on empty queue
ASSERT_EQ(queue_pos_next(pos), 0, "Next should fail on empty queue");
ASSERT_EQ(queue_pos_prev(pos), 0, "Prev should fail on empty queue");
// Test single element queue
struct ll_entry* entry = queue_entry_new(sizeof(struct test_data));
ASSERT_NOT_NULL(entry, "Failed to create entry");
ASSERT_EQ(queue_entry_put(q, entry), 0, "Failed to add single entry");
ASSERT_EQ(queue_entry_count(q), 1, "Wrong count for single entry");
// Test navigation with single element
ASSERT_EQ(queue_pos_seek(pos, 0), 1, "Should seek to single element");
ASSERT_NOT_NULL(queue_pos_current(pos), "Should have current element");
ASSERT_EQ(queue_pos_next(pos), 0, "Next should fail at end");
ASSERT_EQ(queue_pos_seek(pos, 1), 0, "Should fail to seek beyond single element");
// Test removal of single element
struct ll_entry* removed = queue_pos_remove(pos);
ASSERT_NOT_NULL(removed, "Should remove single element");
ASSERT_EQ(queue_entry_count(q), 0, "Queue should be empty after removal");
queue_entry_free(removed);
// Test size limit enforcement
queue_set_size_limit(q, 2);
struct ll_entry* e1 = queue_entry_new(sizeof(struct test_data));
struct ll_entry* e2 = queue_entry_new(sizeof(struct test_data));
struct ll_entry* e3 = queue_entry_new(sizeof(struct test_data));
ASSERT_NOT_NULL(e1, "Failed to create entry 1");
ASSERT_NOT_NULL(e2, "Failed to create entry 2");
ASSERT_NOT_NULL(e3, "Failed to create entry 3");
ASSERT_EQ(queue_entry_put(q, e1), 0, "Should add first entry");
ASSERT_EQ(queue_entry_put(q, e2), 0, "Should add second entry");
ASSERT_EQ(queue_entry_put(q, e3), -1, "Should reject third entry (size limit)");
ASSERT_EQ(queue_entry_count(q), 2, "Wrong count with size limit");
queue_entry_free(e3); // Free the rejected entry
queue_pos_free(pos);
queue_free(q);
uasync_destroy(ua);
TEST_PASS();
}
/* Main test runner */
int main() {
printf("=== Unified Comprehensive ll_queue Test Suite ===\n");
printf("Starting tests at %s\n", __TIME__);
double suite_start_time = get_time_ms();
// Run all tests
test_doubly_linked_basics();
test_position_navigation();
test_position_insert_remove();
test_stress_random_operations();
test_api_compatibility();
test_performance_benchmarks();
test_edge_cases();
double suite_end_time = get_time_ms();
double total_suite_time = suite_end_time - suite_start_time;
printf("\n=== Test Suite Summary ===\n");
printf("Tests run: %d\n", test_stats.tests_run);
printf("Tests passed: %d\n", test_stats.tests_passed);
printf("Tests failed: %d\n", test_stats.tests_failed);
printf("Memory errors: %d\n", test_stats.memory_errors);
printf("Total operations: %d\n", test_stats.operations_count);
printf("Total time: %.2f ms\n", total_suite_time);
printf("Average ops/sec: %.2f\n", test_stats.operations_count * 1000.0 / total_suite_time);
if (test_stats.tests_failed == 0 && test_stats.memory_errors == 0) {
printf("\n✅ ALL TESTS PASSED - ll_queue module is working correctly!\n");
return 0;
} else {
printf("\n❌ TEST FAILURES DETECTED - Check output above for details\n");
return 1;
}
}
Loading…
Cancel
Save