Files
utun2/tests/test_etcp_bbr.c
T

203 lines
8.4 KiB
C

#include "etcp_bbr.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "../lib/debug_config.h"
#define MSEG(s) do { test_total++; printf("TEST %d: %-50s ", test_total, s); fflush(stdout); } while(0)
#define PASS() do { puts("PASS"); test_passed++; } while(0)
#define FAIL(m) do { printf("FAIL: %s\n", m); test_failed++; return -1; } while(0)
static int test_total = 0;
static int test_passed = 0;
static int test_failed = 0;
static uint32_t g_cwnd;
static uint32_t g_pacing;
static uint32_t run_ack_ex(struct bbr* bbr, uint32_t acked, uint32_t rtt_us,
int app, int lost, uint32_t inflight, uint32_t prior_del)
{
bbr->now_tb += rtt_us / 100;
struct bbr_rate_sample rs = {
.delivered = acked, .interval_us = rtt_us, .rtt_us = rtt_us,
.acked_sacked = acked, .prior_delivered = prior_del,
.tx_in_flight = inflight > 0 ? inflight : acked,
.lost = lost, .is_app_limited = app,
};
bbr_main(bbr, &rs, &g_cwnd, &g_pacing, 1400, inflight, inflight >= g_cwnd);
return g_cwnd;
}
static uint32_t run_ack(struct bbr* bbr, uint32_t acked, uint32_t rtt_us,
int app, int lost, uint32_t inflight)
{ return run_ack_ex(bbr, acked, rtt_us, app, lost, inflight, bbr->delivered); }
/* --- tests --- */
/* Одинаковая доставка байтов обязана давать одну скорость независимо от MTU. */
static int test_byte_rate(void) {
MSEG("byte rate and BDP are independent of MSS");
const uint32_t sizes[] = {576, 1400};
for (unsigned i = 0; i < sizeof(sizes)/sizeof(sizes[0]); i++) {
struct bbr s = { .now_tb = 10000 }; bbr_init(&s);
s.mode = BBR_PROBE_BW; s.full_bw_reached = 1; s.cycle_idx = BBR_BW_PROBE_CRUISE;
s.cycle_mstamp_tb = s.now_tb; s.probe_wait_us = 2000000;
uint32_t cwnd = 100000, pacing = 0;
struct bbr_rate_sample rs = { .delivered = 10000, .interval_us = 10000, .rtt_us = 20000,
.acked_sacked = 1000, .tx_in_flight = 40000 };
bbr_main(&s, &rs, &cwnd, &pacing, sizes[i], 40000, 0);
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "BBR byte sample: MSS=%u delivered=10000 interval_us=10000 pace=%u cwnd=%u",
sizes[i], pacing, cwnd);
if (pacing != 990000) FAIL("1MB/s delivery must produce 990000B/s cruise pacing");
if (cwnd < 40000 || cwnd > 41000) FAIL("2*BDP plus ACK aggregation must be 40000..41000 bytes");
}
PASS(); return 0;
}
/* Байтовый Q16 сохраняет многогигабитные скорости; выход uint32 насыщается, а не оборачивается. */
static int test_large_rate(void) {
MSEG("multi-gigabit rate and pacing saturation");
const uint32_t intervals[] = {5, 1}, expected[] = {1980000000, UINT32_MAX};
for (unsigned i = 0; i < 2; i++) {
struct bbr s = { .now_tb = 10000 }; bbr_init(&s);
s.mode = BBR_PROBE_BW; s.full_bw_reached = 1; s.cycle_idx = BBR_BW_PROBE_CRUISE;
s.cycle_mstamp_tb = s.now_tb; s.probe_wait_us = 2000000;
uint32_t cwnd = 100000, pacing = 0;
struct bbr_rate_sample rs = { .delivered = 10000, .interval_us = intervals[i], .rtt_us = 20000,
.acked_sacked = 1000, .tx_in_flight = 40000 };
bbr_main(&s, &rs, &cwnd, &pacing, 1400, 40000, 0);
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "BBR high rate: interval_us=%u pacing=%u expected=%u", intervals[i], pacing, expected[i]);
if (pacing != expected[i]) FAIL("high rate was truncated or wrapped");
}
PASS(); return 0;
}
static int test_init(void)
{
MSEG("bbr_init — startup mode");
struct bbr s; memset(&s, 0xFF, sizeof(s)); bbr_init(&s);
if (s.mode != BBR_STARTUP) FAIL("mode");
if (s.cycle_idx != 0) FAIL("cycle_idx");
if (s.min_rtt_us != ~0U) FAIL("min_rtt_us");
if (s.bw_lo != ~0U) FAIL("bw_lo");
if (s.inflight_lo != ~0U) FAIL("inflight_lo");
if (s.inflight_hi != ~0U) FAIL("inflight_hi");
if (s.initialized != 1) FAIL("initialized");
if (s.full_bw_reached) FAIL("full_bw_reached");
PASS(); return 0;
}
static int test_startup_growth(void)
{
MSEG("startup — cwnd grows");
struct bbr s; memset(&s, 0, sizeof(s)); s.now_tb = 10000; bbr_init(&s);
s.init_cwnd = 4; g_cwnd = 5600; g_pacing = 0;
uint32_t prev = g_cwnd;
for (int r = 0; r < 4; r++) {
// Подтверждаем целое окно за RTT: доступная полоса растёт вместе с окном, STARTUP должен её освоить.
run_ack(&s, prev, 10000, 0, 0, prev);
if (g_cwnd <= prev) FAIL("cwnd did not grow");
prev = g_cwnd;
}
PASS(); return 0;
}
static int test_full_bw(void)
{
MSEG("full_bw → DRAIN → PROBE_BW");
struct bbr s; memset(&s, 0, sizeof(s)); s.now_tb = 10000; bbr_init(&s);
s.init_cwnd = 4; g_cwnd = 5600; g_pacing = 0;
// 4 rounds: first resets full_bw (sample_bw >= 0*1.25), next 3 count up
for (int r = 0; r < 4; r++) {
s.now_tb += 10000;
uint32_t rd = s.delivered;
for (int a = 0; a < 4; a++) run_ack_ex(&s, 1400, 10000, 0, 0, g_cwnd, rd);
}
if (!s.full_bw_reached) FAIL("full_bw_reached");
// Mode transitions STARTUP→DRAIN→PROBE_BW might all happen in one ACK
if (s.mode != BBR_DRAIN && s.mode != BBR_PROBE_BW) FAIL("mode not DRAIN/PROBE_BW after full_bw");
PASS(); return 0;
}
static int test_loss_startup(void)
{
MSEG("loss events in STARTUP → early full_bw");
struct bbr s; memset(&s, 0, sizeof(s)); s.now_tb = 10000; bbr_init(&s);
s.init_cwnd = 4; g_cwnd = 5600; g_pacing = 0; s.loss_round_start = 1;
for (int i = 0; i < 6; i++) { s.now_tb += 1000; run_ack(&s, 1400, 5000, 0, 1, 5600); }
if (!s.full_bw_reached) FAIL("full_bw_reached not set");
PASS(); return 0;
}
static int test_cwnd_bounded(void)
{
MSEG("cwnd capped by inflight_hi");
struct bbr s; memset(&s, 0, sizeof(s)); s.now_tb = 10000; bbr_init(&s);
s.mode = BBR_PROBE_BW; s.full_bw_reached = 1; s.cycle_idx = BBR_BW_PROBE_UP;
s.inflight_hi = 4000; s.inflight_lo = 4000; g_cwnd = 5600; g_pacing = 0;
s.now_tb += 5000; run_ack(&s, 1400, 5000, 0, 0, 5000);
if (g_cwnd > 4000) FAIL("cwnd > inflight_hi");
PASS(); return 0;
}
static int test_loss_cut(void)
{
MSEG("loss cuts inflight_lo");
struct bbr s; memset(&s, 0, sizeof(s)); s.now_tb = 10000; bbr_init(&s);
s.mode = BBR_PROBE_BW; s.full_bw_reached = 1; s.cycle_idx = BBR_BW_PROBE_CRUISE;
s.inflight_lo = 5000; s.bw_lo = 1000;
s.bw_latest = 500; s.inflight_latest = 3000;
s.delivered = 0; s.loss_round_delivered = 0;
s.cycle_mstamp_tb = s.now_tb; // prevent phase advance
s.probe_wait_us = 2000000;
uint32_t prev = s.inflight_lo;
s.now_tb += 5000; run_ack_ex(&s, 1400, 5000, 0, 1, 5000, 0);
if (s.inflight_lo >= prev) FAIL("inflight_lo not reduced");
PASS(); return 0;
}
static int test_fast_path(void)
{
MSEG("app-limited ACK triggers fast_path");
struct bbr s; memset(&s, 0, sizeof(s)); s.now_tb = 10000; bbr_init(&s);
s.init_cwnd = 4; s.full_bw_reached = 1;
s.mode = BBR_PROBE_BW; s.cycle_idx = BBR_BW_PROBE_CRUISE;
s.try_fast_path = 1; s.bw_hi[0] = 10000; s.bw_lo = 5000;
g_cwnd = 5600; g_pacing = 0;
s.now_tb += 1000; run_ack(&s, 1400, 5000, 1, 0, 2000);
PASS(); return 0;
}
static int test_probe_rtt(void)
{
MSEG("PROBE_RTT entry after 5s");
struct bbr s; memset(&s, 0, sizeof(s)); s.now_tb = 10000; bbr_init(&s);
s.mode = BBR_PROBE_BW; s.full_bw_reached = 1; s.cycle_idx = BBR_BW_PROBE_CRUISE;
s.probe_rtt_min_us = ~0U; s.probe_rtt_min_stamp_tb = 10000;
s.min_rtt_us = ~0U; s.min_rtt_stamp_tb = 10000;
g_cwnd = 5600; g_pacing = 0;
s.now_tb = 10000 + 5000U * 10; // +5 seconds in 0.1ms
run_ack(&s, 1400, 1000, 0, 0, 5600);
if (s.mode != BBR_PROBE_RTT) FAIL("mode not PROBE_RTT");
if (g_cwnd > BBR_CWND_MIN_TARGET * 1400 + 1400) FAIL("cwnd not capped");
PASS(); return 0;
}
int main(void)
{
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); debug_set_category_level(DEBUG_CATEGORY_DEBUG, DEBUG_LEVEL_INFO);
test_byte_rate();
test_large_rate();
test_init();
test_startup_growth();
test_full_bw();
test_loss_startup();
test_cwnd_bounded();
test_loss_cut();
test_fast_path();
test_probe_rtt();
printf("\n=== Results: %d/%d passed, %d failed ===\n", test_passed, test_total, test_failed);
return test_failed ? 1 : 0;
}