Files
utun2/tests/test_etcp_windows.c
T

463 lines
24 KiB
C

/* Настоящие очереди/таймеры ETCP; управляемые часы, перехват только передачи и отказа аллокации. */
#include <stdlib.h>
#include <string.h>
#include "etcp.h"
#include "etcp_api.h"
#include "etcp_bbr.h"
#include "etcp_loadbalancer.h"
#include "config_parser.h"
#include "utun_instance.h"
#include "test_clock.h"
#include "../lib/mem.h"
#define CHECK(x) do { if (!(x)) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "FAIL %s:%d: %s", __func__, __LINE__, #x); exit(1); } } while (0)
static struct UASYNC* ua;
static struct UTUN_INSTANCE inst;
static struct utun_config cfg;
static struct ETCP_CONN* conn;
static struct ETCP_LINK *a, *b;
static unsigned sends, fail_alloc, fail_send, fail_tx_timer, bbr_calls, saw_cwnd_limited, saw_app_limited;
static unsigned fail_shaper_timer;
static struct bbr_rate_sample last_sample;
static uint32_t last_mss;
void* __real_uasync_call_soon(struct UASYNC*, void*, timeout_callback_t);
void* __wrap_uasync_call_soon(struct UASYNC* loop, void* arg, timeout_callback_t callback) {
if (fail_tx_timer && arg == conn) { fail_tx_timer--; return NULL; }
return __real_uasync_call_soon(loop, arg, callback);
}
void* __real_uasync_set_timeout(struct UASYNC*, int, void*, timeout_callback_t, const char*);
void* __wrap_uasync_set_timeout(struct UASYNC* loop, int delay, void* arg, timeout_callback_t callback, const char* name) {
if (fail_shaper_timer && !strcmp(name, "etcp_shaper")) { fail_shaper_timer--; return NULL; }
if (fail_tx_timer && !strcmp(name, "etcp_tx")) { fail_tx_timer--; return NULL; }
return __real_uasync_set_timeout(loop, delay, arg, callback, name);
}
int __wrap_etcp_encrypt_send(struct ETCP_DGRAM* pkt) {
CHECK(conn->tx_running && pkt->link && pkt->link->initialized && pkt->link->link_status);
sends++;
if (fail_send) { fail_send--; return -1; }
return pkt->data_len;
}
void* __real_memory_pool_alloc_impl(struct memory_pool* pool, const char* location);
void* __wrap_memory_pool_alloc_impl(struct memory_pool* pool, const char* location) {
if (pool == inst.pkt_pool && fail_alloc) { fail_alloc--; return NULL; }
return __real_memory_pool_alloc_impl(pool, location);
}
void __real_bbr_main(struct bbr*, const struct bbr_rate_sample*, uint32_t*, uint32_t*, uint32_t, uint32_t, int);
void __wrap_bbr_main(struct bbr* model, const struct bbr_rate_sample* sample, uint32_t* cwnd,
uint32_t* pacing, uint32_t mss, uint32_t inflight, int limited) {
bbr_calls++; saw_cwnd_limited |= !!limited; saw_app_limited |= !!sample->is_app_limited;
last_sample = *sample;
last_mss = mss;
__real_bbr_main(model, sample, cwnd, pacing, mss, inflight, limited);
}
/* Счётчики сверяются с содержимым очередей, а не с ожидаемыми результатами самого алгоритма. */
static void check_accounting(void) {
for (struct ETCP_LINK* l = conn->links; l; l = l->next) {
uint32_t bytes = 0, packets = 0;
for (struct ll_entry* e = conn->input_wait_ack->head; e; e = e->next) {
struct INFLIGHT_PACKET* p = (struct INFLIGHT_PACKET*)e;
CHECK(p->state == INFLIGHT_STATE_WAIT_ACK);
if (p->last_link == l) { bytes += p->ll.len; packets++; }
}
CHECK(l->inflight_bytes == bytes && l->inflight_packets == packets);
}
for (struct ll_entry* e = conn->input_send_q->head; e; e = e->next)
CHECK(((struct INFLIGHT_PACKET*)e)->state == INFLIGHT_STATE_WAIT_SEND);
CHECK(conn->unacked_bytes == conn->input_send_q->total_bytes + conn->input_wait_ack->total_bytes);
}
static void setup(void) {
memset(&inst, 0, sizeof(inst)); memset(&cfg, 0, sizeof(cfg));
sends = fail_alloc = fail_send = fail_tx_timer = bbr_calls = saw_cwnd_limited = saw_app_limited = 0;
fail_shaper_timer = 0;
ua = uasync_create(); CHECK(ua);
inst.ua = ua; inst.node_id = 1; inst.config = &cfg; cfg.global.bbr_max_cwnd = INFLIGHT_LIM_MAX;
inst.connections = queue_new(ua, 16, 0, 8, "windows");
inst.pkt_pool = memory_pool_init(sizeof(struct ETCP_DGRAM) + PACKET_DATA_SIZE, "window_packets");
inst.data_pool = memory_pool_init(PACKET_DATA_SIZE, "window_data");
inst.ack_pool = memory_pool_init(sizeof(struct ACK_PACKET), "window_acks");
CHECK(inst.connections && inst.pkt_pool && inst.data_pool && inst.ack_pool);
conn = etcp_connection_create(&inst, "windows"); CHECK(conn);
conn->peer_node_id = 2; conn->links_up = 1;
etcp_conn_ready(conn);
a = u_calloc(1, sizeof(*a)); b = u_calloc(1, sizeof(*b)); CHECK(a && b);
a->next = b; conn->links = a;
unsigned id = 0;
for (struct ETCP_LINK* l = a; l; l = l->next) {
l->etcp = conn; l->local_link_id = ++id; l->initialized = l->link_status = 1;
l->mtu = 1280; l->remote_addr.ss_family = AF_INET; l->inflight_lim_bytes = 65536;
}
etcp_conn_on_inflight_lim_changed(conn);
}
static void teardown(void) {
check_accounting();
etcp_connection_close(conn);
uasync_poll(ua, 0);
queue_free(inst.connections);
memory_pool_destroy(inst.pkt_pool); memory_pool_destroy(inst.data_pool); memory_pool_destroy(inst.ack_pool);
uasync_poll(ua, 0);
CHECK(ua->timer_alloc_count == ua->timer_free_count);
uasync_destroy(ua, 0);
}
/* Вход отправителя уже после normalizer: фрагмент остаётся в реальной indexed send_q. */
static struct INFLIGHT_PACKET* enqueue(unsigned len) {
struct INFLIGHT_PACKET* p = (struct INFLIGHT_PACKET*)queue_entry_new_from_pool(conn->inflight_pool); CHECK(p);
p->seq = conn->next_tx_id++; p->state = INFLIGHT_STATE_WAIT_SEND;
p->ll.len = len; p->ll.dgram = memory_pool_alloc(inst.data_pool); p->ll.dgram_pool = inst.data_pool;
CHECK(p->ll.dgram); memset(p->ll.dgram, 0x5a, len);
conn->unacked_bytes += len;
CHECK(queue_data_put_with_index(conn->input_send_q, &p->ll) == 0);
etcp_conn_wake_sender(conn);
return p;
}
static struct ETCP_DGRAM* request(void) {
struct ETCP_DGRAM* pkt = etcp_request_pkt(conn);
check_accounting();
return pkt;
}
static void fill(struct ETCP_LINK* target, unsigned count, unsigned len) {
int up_a = a->link_status, up_b = b->link_status;
a->link_status = target == a; b->link_status = target == b;
for (unsigned i = 0; i < count; i++) {
enqueue(len);
struct ETCP_DGRAM* pkt = request(); CHECK(pkt && pkt->link == target);
memory_pool_free(inst.pkt_pool, pkt);
}
a->link_status = up_a; b->link_status = up_b;
}
static void ack(uint32_t seq) { etcp_ack_recv(conn, seq, 0, 0); check_accounting(); }
static void ack_all(void) {
while (conn->input_wait_ack->head) ack(((struct INFLIGHT_PACKET*)conn->input_wait_ack->head)->seq);
}
/* Ждём наблюдаемое событие, а не точную границу миллисекундного таймера uasync. */
static void wait_sends(unsigned expected) {
uint64_t end = get_time_tb() + 20000;
while (sends < expected && get_time_tb() < end) test_clock_poll(ua, 1);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "TX observed=%u expected=%u send_q=%d wait_ack=%d", sends, expected,
conn->input_send_q->count, conn->input_wait_ack->count);
CHECK(sends == expected); check_accounting();
}
/* В полном пути свободное окно другого линка не разрешает DATA через заполненный. */
static void selection(void) {
setup(); fill(a, 8, 1024); fill(b, 16, 1024); a->inflight_lim_bytes = 8192;
enqueue(512);
struct ETCP_DGRAM* pkt = request(); CHECK(pkt && pkt->link == b);
CHECK(a->inflight_bytes == 8192 && b->inflight_bytes == 16896);
memory_pool_free(inst.pkt_pool, pkt);
teardown();
}
static void boundaries_ack_burst(void) {
setup(); b->link_status = 0; a->inflight_lim_bytes = 8192;
fill(a, 7, 1024); enqueue(1024);
struct ETCP_DGRAM* pkt = request(); CHECK(pkt && a->inflight_bytes == 8192);
memory_pool_free(inst.pkt_pool, pkt);
struct INFLIGHT_PACKET* waiting = enqueue(1); CHECK(!request() && a->cwnd_limited);
struct ACK_PACKET* confirmation = (struct ACK_PACKET*)queue_entry_new_from_pool(inst.ack_pool); CHECK(confirmation);
confirmation->seq = 777;
CHECK(queue_data_put_with_index(conn->ack_q, &confirmation->ll) == 0);
pkt = request(); CHECK(pkt && pkt->data[0] == ETCP_SECTION_ACK && pkt->data[1] == 1);
CHECK(waiting == (struct INFLIGHT_PACKET*)conn->input_send_q->head && a->inflight_bytes == 8192);
memory_pool_free(inst.pkt_pool, pkt);
a->burst_active = 1; a->burst_count = 2;
for (int i = 0; i < 2; i++) {
pkt = request(); CHECK(pkt && pkt->data[8] == ETCP_SECTION_MEAS_TS && (pkt->data[11] & MEAS_FLAG_IS_FILLER));
CHECK(a->inflight_bytes == 8192 && conn->input_send_q->head == &waiting->ll);
memory_pool_free(inst.pkt_pool, pkt);
}
CHECK(!a->burst_active);
teardown();
}
/* RTO освобождает окно; поздний ACK удаляет ожидающий retry, затем остальные повторы переходят A -> B. */
static void late_ack_and_migration(void) {
setup(); fill(a, 8, 1024); enqueue(600);
a->link_status = b->link_status = 0;
test_clock_run_for(ua, 120);
check_accounting(); CHECK(!conn->input_wait_ack->count && !a->inflight_bytes);
ack(1); ack(1); CHECK(!a->inflight_bytes);
b->link_status = 1; etcp_link_update_inflight_lim(b, 8192);
wait_sends(8);
CHECK(!conn->input_send_q->count && b->inflight_bytes == 7768 && sends == 8);
for (struct ll_entry* e = conn->input_wait_ack->head; e; e = e->next)
CHECK(((struct INFLIGHT_PACKET*)e)->last_link == b);
ack_all(); teardown();
}
static void lost_ack_and_shrink(void) {
setup(); b->link_status = 0; a->inflight_lim_bytes = 16384; fill(a, 16, 1024);
etcp_link_update_inflight_lim(a, 8192);
enqueue(1024); CHECK(!request() && a->inflight_bytes == 16384);
wait_sends(8);
check_accounting(); CHECK(sends == 8 && a->inflight_bytes == 8192 && conn->input_send_q->count == 9);
ack_all(); wait_sends(16);
CHECK(sends == 16 && a->inflight_bytes == 8192 && conn->input_send_q->count == 1);
ack_all(); wait_sends(17); ack_all();
CHECK(!conn->unacked_bytes && !conn->input_send_q->count);
teardown();
}
static void grow_and_session(void) {
setup(); b->link_status = 0; a->inflight_lim_bytes = 8192; fill(a, 8, 1024); enqueue(100);
CHECK(!request());
uint64_t allocated = ua->timer_alloc_count;
test_clock_run_for(ua, 20); CHECK(!sends && ua->timer_alloc_count - allocated < 4);
etcp_link_update_inflight_lim(a, 16384); wait_sends(1);
CHECK(sends == 1 && a->inflight_bytes == 8292); ack_all();
conn->initialized = 0; conn->session_required = conn->reinit_pending = 1;
enqueue(100); CHECK(!request()); test_clock_run_for(ua, 10); CHECK(sends == 1);
conn->session_required = 0; etcp_conn_ready(conn); wait_sends(2);
CHECK(sends == 2 && conn->input_wait_ack->count == 1);
ack_all(); teardown();
}
static void allocation_and_send_failure(void) {
setup(); b->link_status = 0; enqueue(100); fail_alloc = 1;
test_clock_run_for(ua, 20);
CHECK(!sends && conn->input_send_q->count == 1 && !a->inflight_bytes && conn->tx_token && conn->tx_delayed);
fail_send = 1; wait_sends(1);
CHECK(sends == 1 && conn->input_wait_ack->count == 1);
wait_sends(2);
ack_all(); teardown();
}
static void removed_link_and_bbr(void) {
setup(); fill(a, 1, 1024);
etcp_link_close(a); a = NULL;
ack(1); CHECK(!conn->unacked_bytes);
b->bbr = u_calloc(1, sizeof(*b->bbr)); CHECK(b->bbr); bbr_init(b->bbr);
b->inflight_lim_bytes = 8192;
for (int i = 0; i < 7; i++) enqueue(1024);
for (int i = 0; i < 7; i++) {
struct ETCP_DGRAM* pkt = request(); CHECK(pkt); memory_pool_free(inst.pkt_pool, pkt);
}
enqueue(1025); CHECK(!request() && b->cwnd_limited && b->inflight_bytes < b->inflight_lim_bytes);
// Первый ACK относится к отправке при наличии ожидающих данных: она не app_limited.
struct INFLIGHT_PACKET* first = (struct INFLIGHT_PACKET*)conn->input_wait_ack->head;
CHECK(!first->is_app_limited);
ack(first->seq); CHECK(bbr_calls == 1 && saw_cwnd_limited && !saw_app_limited);
teardown();
}
static void shaper_ready(void* arg) {
struct ETCP_LINK* link = arg;
link->shaper_timer = NULL;
loadbalancer_link_ready(link);
}
/* Реальный RTO: нижняя/верхняя границы и влияние RTT/jitter, без подмены обработчика таймера. */
static void retransmission_timeout(void) {
const struct { uint16_t rtt, jitter; uint32_t expected_tb; } cases[] = {
{0, 0, 100}, {20, 10, 100}, {200, 0, 400}, {200, 75, 550},
{2000, 100, 4200}, {6000, 1000, 10000}, {UINT16_MAX, UINT16_MAX, 10000}
};
for (unsigned i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
setup(); b->link_status = 0;
conn->rtt_avg_10 = cases[i].rtt; conn->jitter = cases[i].jitter;
fill(a, 1, 100);
struct INFLIGHT_PACKET* packet = (struct INFLIGHT_PACKET*)conn->input_wait_ack->head;
uint64_t sent = packet->last_timestamp, deadline = sent + cases[i].expected_tb + 1000;
// Блокируем только новую передачу: статус UP и настоящий retrans-таймер сохраняются.
a->shaper_timer = uasync_set_timeout(ua, cases[i].expected_tb + 10000, a, shaper_ready, "test_rto_shaper");
CHECK(a->shaper_timer);
while (packet->state == INFLIGHT_STATE_WAIT_ACK && get_time_tb() < deadline) test_clock_poll(ua, 10);
uint64_t elapsed = get_time_tb() - sent;
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "RTO: RTT_tb=%u jitter_tb=%u expected_tb=%u observed_tb=%llu state=%u",
cases[i].rtt, cases[i].jitter, cases[i].expected_tb, (unsigned long long)elapsed, packet->state);
CHECK(packet->state == INFLIGHT_STATE_WAIT_SEND && elapsed >= cases[i].expected_tb && elapsed <= cases[i].expected_tb + 1000);
CHECK(!sends && !a->inflight_bytes && conn->input_send_q->count == 1 && conn->unacked_bytes == 100);
check_accounting(); ack(packet->seq); teardown();
}
}
static void pacing_reset_close(void) {
setup(); b->link_status = 0;
a->shaper_timer = uasync_set_timeout(ua, 20, a, shaper_ready, "test_shaper"); CHECK(a->shaper_timer);
enqueue(100); CHECK(!request());
wait_sends(1); ack_all();
enqueue(100); CHECK(conn->tx_token);
etcp_conn_reinit_id(conn, "queued sender reset", conn->reset_id);
CHECK(!conn->tx_token && !conn->unacked_bytes && conn->reinit_pending);
etcp_conn_ready(conn);
enqueue(100); CHECK(conn->tx_token);
CHECK(etcp_conn_ref_take(conn) == 0);
etcp_connection_close(conn); CHECK(conn->state == 2 && !conn->tx_token);
etcp_conn_ref_free(conn);
teardown();
}
/* Смена ставки пересчитывает долг, а не оставляет таймер от прежней скорости. */
static void pacing_rate_changes(void) {
setup(); b->link_status = 0; conn->rtt_avg_10 = 10000;
etcp_loadbalancer_set_pacing_rate(a, 100);
enqueue(1000); wait_sends(1); enqueue(500);
uint64_t debt = a->shaper_load_time_tb - get_time_tb();
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "Pacing below 1Kbit/s: rate=%u debt_tb=%llu", a->bbr_pacing_rate, (unsigned long long)debt);
CHECK(a->shaper_timer && a->bbr_pacing_rate == 100 && debt >= 99000 && debt <= 103000);
etcp_loadbalancer_set_pacing_rate(a, 1000000);
CHECK(a->shaper_load_time_tb <= get_time_tb() + 20);
wait_sends(2); ack_all(); teardown();
setup(); b->link_status = 0; conn->rtt_avg_10 = 10000;
etcp_loadbalancer_set_pacing_rate(a, 1000000);
enqueue(1000); wait_sends(1); enqueue(500);
etcp_loadbalancer_set_pacing_rate(a, 100);
CHECK(a->shaper_timer && a->shaper_load_time_tb > get_time_tb() + 1000);
test_clock_run_for(ua, 200); CHECK(sends == 1 && conn->input_send_q->count == 1);
etcp_loadbalancer_set_pacing_rate(a, 0);
CHECK(!a->shaper_timer && !a->shaper_sub_nanotime);
wait_sends(2); ack_all(); teardown();
}
static void pacing_timer_failure(void) {
setup(); CHECK(etcp_conn_ref_take(conn) == 0); b->link_status = 0;
etcp_loadbalancer_set_pacing_rate(a, 100); fail_shaper_timer = 1;
enqueue(100); uasync_drain_immediate(ua);
CHECK(!fail_shaper_timer && conn->state == 2 && !conn->links);
etcp_conn_ref_free(conn); teardown();
}
/* Отправитель не превращает pending transport в UP до первого INIT/подтверждения сессии. */
static void initial_readiness(void) {
setup(); conn->state = 0; conn->initialized = conn->links_up = conn->tx_state = 0;
conn->session_required = 1; enqueue(100);
test_clock_run_for(ua, 20);
CHECK(!conn->tx_state && !sends && !conn->links_up);
conn->link_ready_for_send_fn(conn); CHECK(!conn->links_up);
conn->session_required = 0; etcp_conn_ready(conn); wait_sends(1);
CHECK(conn->state == 1 && conn->links_up && conn->initialized);
ack_all(); teardown();
}
static void config_limits(void) {
const char* values[] = {"0", "8191", "-1", "2147483648", "999999999999999999999", "abc", "8192x", "8192", "65536"};
for (unsigned i = 0; i < sizeof(values) / sizeof(values[0]); i++) {
char text[128]; snprintf(text, sizeof(text), "[global]\nbbr_max_cwnd=%s\n", values[i]);
struct utun_config* parsed = parse_config_from_buf(text, strlen(text), "window_config");
CHECK((parsed != NULL) == (i >= 7));
free_config(parsed);
}
}
/* Отказ TX-таймера закрывает транспорт после выхода из вызывающего стека, без UAF очереди/линков. */
static void timer_failure(void) {
setup(); CHECK(!conn->tx_token && etcp_conn_ref_take(conn) == 0);
fail_tx_timer = 1; enqueue(100);
CHECK(conn->close_requested && conn->close_token && conn->state != 2 && conn->links);
uasync_drain_immediate(ua);
CHECK(conn->state == 2 && !conn->tx_token && !conn->links);
etcp_conn_ref_free(conn); teardown();
}
static void immediate_send(void) {
setup(); enqueue(100);
CHECK(conn->tx_token && !conn->tx_delayed);
uasync_drain_immediate(ua);
CHECK(sends == 1 && conn->input_wait_ack->count == 1);
ack_all(); teardown();
}
/* Подтверждения входящего потока не должны ждать pacing собственного исходящего DATA. */
static void ack_without_data_pacing(void) {
setup(); b->link_status = 0; etcp_loadbalancer_set_pacing_rate(a, 125);
a->shaper_timer = uasync_set_timeout(ua, 1000, a, shaper_ready, "test_shaper"); CHECK(a->shaper_timer);
void* timer = a->shaper_timer;
uint64_t load = a->shaper_load_time_tb, fraction = a->shaper_sub_nanotime;
struct ACK_PACKET* confirmation = (struct ACK_PACKET*)queue_entry_new_from_pool(inst.ack_pool); CHECK(confirmation);
confirmation->seq = 777;
CHECK(queue_data_put_with_index(conn->ack_q, &confirmation->ll) == 0);
enqueue(100); uasync_drain_immediate(ua);
CHECK(sends == 1 && conn->input_send_q->count == 1 && !conn->ack_q->count);
CHECK(a->shaper_timer == timer && a->shaper_load_time_tb == load && a->shaper_sub_nanotime == fraction);
uasync_cancel_timeout(ua, timer); a->shaper_timer = NULL;
etcp_conn_wake_sender(conn); wait_sends(2); ack_all(); teardown();
}
/* ACK одной пачки приходят в один tick: скорость определяется доставкой всей группы, а не промежутком между ACK. */
static void bbr_delivery_clock(void) {
setup(); b->link_status = 0; conn->rtt_avg_10 = 200;
a->bbr = u_calloc(1, sizeof(*a->bbr)); CHECK(a->bbr); bbr_init(a->bbr);
fill(a, 8, 1000);
uint64_t sent_tb = get_time_tb();
test_clock_run_for(ua, 200);
for (unsigned seq = 1; seq <= 8; seq++) {
ack(seq);
uint64_t elapsed_us = (get_time_tb() - sent_tb) * 100;
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "Delivery sample: seq=%u delivered=%u acked=%u interval_us=%u flight_us=%llu",
seq, last_sample.delivered, last_sample.acked_sacked, last_sample.interval_us, (unsigned long long)elapsed_us);
CHECK(last_sample.delivered == seq * 1000 && last_sample.acked_sacked == 1000);
CHECK(last_sample.interval_us >= 19000 && last_sample.interval_us <= elapsed_us + 1000);
}
CHECK(a->bbr->delivered == 8000);
teardown();
}
/* Модель и транспорт используют один DATA MSS и одно окно, в том числе ниже 8KiB. */
static void bbr_probe_window(void) {
setup(); b->link_status = 0; conn->rtt_avg_10 = 10000;
etcp_update_mtu(conn);
uint32_t minimum = BBR_CWND_MIN_TARGET * conn->normalizer->frag_size;
a->rtt_last = 100;
a->bbr = u_calloc(1, sizeof(*a->bbr)); CHECK(a->bbr);
a->bbr->now_tb = 10000; bbr_init(a->bbr);
a->bbr->mode = BBR_PROBE_BW; a->bbr->full_bw_reached = 1; a->bbr->cycle_idx = BBR_BW_PROBE_CRUISE;
a->bbr->min_rtt_us = a->bbr->probe_rtt_min_us = 10000; a->bbr->has_seen_rtt = 1;
a->bbr->bw_hi[0] = BW_UNIT / 10; a->bbr->now_tb += BBR_PROBE_RTT_WIN_MS * 10 + 1;
fill(a, 8, 1000); test_clock_run_for(ua, 200); ack(1);
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PROBE_RTT transport: DATA_MSS=%u model_MSS=%u window=%u expected=%u",
conn->normalizer->frag_size, last_mss, a->inflight_lim_bytes, minimum);
CHECK(last_mss == conn->normalizer->frag_size && a->inflight_lim_bytes == minimum && minimum < INFLIGHT_LIM_MIN);
struct INFLIGHT_PACKET* probe = enqueue(1000); CHECK(!request());
ack(2); ack(3); ack(4); CHECK(a->bbr->probe_rtt_done_stamp_tb);
ack(5); // оставшиеся 3000 байт и новый фрагмент целиком помещаются в 4388
struct ETCP_DGRAM* pkt = request(); CHECK(pkt && probe->is_app_limited);
memory_pool_free(inst.pkt_pool, pkt);
test_clock_run_for(ua, 10); a->bbr->now_tb += BBR_PROBE_RTT_MS * 10;
ack(probe->seq); CHECK(a->bbr->mode == BBR_PROBE_BW);
uint32_t window = a->inflight_lim_bytes;
etcp_link_update_inflight_lim(a, 0); CHECK(a->inflight_lim_bytes == window);
etcp_link_update_inflight_lim(a, UINT32_MAX); CHECK(a->inflight_lim_bytes == conn->max_inflight);
ack_all(); teardown();
}
/* Размер STCP-кадра 65535 не является размером DATA-фрагмента и не задаёт начальное окно. */
static void tcp_initial_window(void) {
setup();
struct ETCP_LINK* link = etcp_link_new(conn, NULL, NULL, 1); CHECK(link && link->is_tcp);
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "STCP initial window: frame=%u DATA_MSS=%u window=%u", link->mtu,
conn->normalizer->frag_size, link->inflight_lim_bytes);
CHECK(link->inflight_lim_bytes == BBR_CWND_MIN_TARGET * conn->normalizer->frag_size);
CHECK(link->bbr->init_cwnd == BBR_CWND_MIN_TARGET);
teardown();
}
int main(void) {
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR);
if (getenv("UTUN_TEST_DEBUG")) debug_set_category_level(DEBUG_CATEGORY_ETCP, DEBUG_LEVEL_DEBUG);
test_clock_init(10);
size_t baseline = u_get_allocated_count();
void (*cases[])(void) = {selection, boundaries_ack_burst, late_ack_and_migration, lost_ack_and_shrink,
grow_and_session, allocation_and_send_failure, removed_link_and_bbr, pacing_reset_close, initial_readiness,
config_limits, timer_failure, immediate_send, ack_without_data_pacing, bbr_delivery_clock, bbr_probe_window, tcp_initial_window,
retransmission_timeout, pacing_rate_changes, pacing_timer_failure};
for (unsigned i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
cases[i](); CHECK(u_get_allocated_count() == baseline);
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PASS ETCP window scenario %u", i);
}
test_clock_finish();
return 0;
}