Browse Source
- 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
35 changed files with 6943 additions and 601 deletions
@ -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 |
||||||
@ -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 |
||||||
@ -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; |
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
@ -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 |
||||||
@ -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 |
||||||
@ -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 |
||||||
@ -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 |
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
@ -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)); |
||||||
|
} |
||||||
|
} |
||||||
@ -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
|
||||||
@ -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 -> |
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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; |
||||||
|
} |
||||||
@ -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…
Reference in new issue