Skip to content

Commit 201b869

Browse files
committed
Final iteration
1 parent 377dbd9 commit 201b869

File tree

1 file changed

+35
-157
lines changed

1 file changed

+35
-157
lines changed

src/core/unittest/CubicTest.cpp

Lines changed: 35 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -762,30 +762,6 @@ TEST_F(CubicTest, Pacing_CongestionAvoidanceEstimation)
762762
ASSERT_EQ(Allowance, ExpectedAllowance);
763763
}
764764

765-
//
766-
// Test: Pacing SendAllowance Capped at Available Window
767-
// Scenario: When the pacing formula produces an allowance larger than the available
768-
// congestion window (CW − BytesInFlight), it is capped to the available window.
769-
// A large TimeSinceLastSend (1 second >> SmoothedRtt of 50ms) causes the pacing
770-
// calculation to exceed the available space, exercising the cap.
771-
//
772-
TEST_F(CubicTest, Pacing_CappedAtAvailableWindow)
773-
{
774-
InitializeDefaultWithRtt(/*WindowPackets = */ 10, /*HyStart = */ false);
775-
Connection.Settings.PacingEnabled = TRUE;
776-
777-
// Send data
778-
CC->QuicCongestionControlOnDataSent(CC, 1000);
779-
780-
// Large time delta: pacing formula produces (24640 * 1000000) / 50000 = 492800,
781-
// which far exceeds available window (12320 - 1000 = 11320), triggering the cap.
782-
uint32_t Allowance = CC->QuicCongestionControlGetSendAllowance(CC, 1000000, TRUE);
783-
784-
// Capped at available window
785-
uint32_t AvailableWindow = Cubic->CongestionWindow - Cubic->BytesInFlight;
786-
ASSERT_EQ(Allowance, AvailableWindow);
787-
}
788-
789765
//
790766
// Test: Congestion Avoidance AIMD vs CUBIC Window Selection
791767
// Scenario: After loss triggers recovery and an ACK exits recovery, a subsequent
@@ -812,19 +788,35 @@ TEST_F(CubicTest, CongestionAvoidance_AIMDvsCubicSelection)
812788
// CUBIC formula and AIMD both execute, max() selects the winner.
813789
ASSERT_FALSE(Cubic->IsInRecovery);
814790

815-
// Derivation of expected CW:
816-
// KCubic = CubeRoot((24640/1232 * 3 << 9) / 4) = CubeRoot(7680) = 19
817-
// KCubic = S_TO_MS(19) = 19000; KCubic >>= 3 = 2375
818-
// TimeInCongAvoid = 1100000 - 1050000 = 50000 µs
819-
// DeltaT = US_TO_MS(50000 - 2375000 + 50000) = -2275
820-
// CubicWindow ≈ ((-2275²>>10) * -2275 * 492 >> 20) + 24640 ≈ 19245
821-
// AimdWindow(17248) < CubicWindow(19245) → CUBIC path selected
822-
// TargetWindow = max(17248, min(19245, 17248+8624)) = 19245
823-
// Growth = (19245 - 17248) * 1232 / 17248 = 142
824-
// Expected CW = 17248 + 142 = 17390
791+
// Reproduce the CUBIC formula to compute expected CubicWindow.
792+
// This mirrors the production code (OnCongestionEvent + OnDataAcknowledged).
793+
uint32_t WindowMax = Cubic->WindowMax; // Set by EnterCongestionAvoidance
794+
uint32_t KCubic = CubeRoot((WindowMax / DatagramPayloadLength * 3 << 9) / 4);
795+
KCubic = KCubic * 1000; // S_TO_MS
796+
KCubic >>= 3;
797+
798+
uint64_t TimeInCongAvoidUs = CongAvoidAck.TimeNow - Cubic->TimeOfCongAvoidStart;
799+
int64_t DeltaT =
800+
((int64_t)TimeInCongAvoidUs -
801+
(int64_t)KCubic * 1000 + // MS_TO_US
802+
(int64_t)CongAvoidAck.SmoothedRtt) / 1000; // US_TO_MS
803+
804+
int64_t CubicWindow =
805+
((((DeltaT * DeltaT) >> 10) * DeltaT *
806+
(int64_t)(DatagramPayloadLength * 4 / 10)) >> 20) +
807+
(int64_t)WindowMax;
808+
809+
// CUBIC > AIMD → bounded growth path selected
810+
ASSERT_GT(CubicWindow, (int64_t)Cubic->AimdWindow);
811+
812+
uint64_t TargetWindow = CXPLAT_MAX(
813+
WindowBeforeCongAvoid,
814+
CXPLAT_MIN((uint64_t)CubicWindow, (uint64_t)WindowBeforeCongAvoid + (WindowBeforeCongAvoid >> 1)));
815+
uint32_t ExpectedGrowth =
816+
(uint32_t)(((TargetWindow - WindowBeforeCongAvoid) * DatagramPayloadLength) / WindowBeforeCongAvoid);
817+
825818
ASSERT_GT(Cubic->CongestionWindow, WindowBeforeCongAvoid);
826-
ASSERT_LE(Cubic->CongestionWindow, WindowBeforeCongAvoid + (WindowBeforeCongAvoid >> 1));
827-
ASSERT_EQ(Cubic->CongestionWindow, WindowAfterLoss + (uint32_t)(((uint64_t)(19245 - WindowAfterLoss) * DatagramPayloadLength) / WindowAfterLoss));
819+
ASSERT_EQ(Cubic->CongestionWindow, WindowBeforeCongAvoid + ExpectedGrowth);
828820
}
829821

830822
//
@@ -1034,49 +1026,6 @@ TEST_F(CubicTest, CubicWindow_OverflowToBytesInFlightMax)
10341026
);
10351027
}
10361028

1037-
//
1038-
// Test: Slow Start Window Growth and Send Allowance After ACK
1039-
// Scenario: After filling the congestion window completely (blocked), an ACK frees
1040-
// space and triggers slow-start growth. Verifies window grows by BytesAcked,
1041-
// BytesInFlight decreases, and GetSendAllowance reflects the new available window.
1042-
//
1043-
TEST_F(CubicTest, SlowStart_WindowGrowthAndAllowance)
1044-
{
1045-
InitializeWithDefaults();
1046-
const uint16_t DatagramPayloadLength = QuicPathGetDatagramPayloadSize(&Connection.Paths[0]);
1047-
// MTU=1280, payload = 1280 - 48 = 1232
1048-
// InitialWindow = 10 * 1232 = 12320
1049-
uint32_t InitialWindow = DatagramPayloadLength * Settings.InitialWindowPackets;
1050-
ASSERT_EQ(Cubic->CongestionWindow, InitialWindow);
1051-
1052-
// Start with blocked state by filling the window
1053-
CC->QuicCongestionControlOnDataSent(CC, Cubic->CongestionWindow);
1054-
// BytesInFlight = 12320
1055-
1056-
// Now free up space by acknowledging half the data
1057-
uint32_t BytesAcked = InitialWindow / 2; // 6160
1058-
QUIC_ACK_EVENT AckEvent = MakeAckEvent(1000000, 5, 10, BytesAcked);
1059-
1060-
CC->QuicCongestionControlOnDataAcknowledged(CC, &AckEvent);
1061-
1062-
// After ACK in slow start:
1063-
// - BytesInFlight = 12320 - 6160 = 6160
1064-
// - Window grows by BytesAcked = 6160 (slow start)
1065-
// - NewWindow = 12320 + 6160 = 18480
1066-
uint32_t NewWindow = InitialWindow + BytesAcked;
1067-
uint32_t BytesInFlightAfterAck = InitialWindow - BytesAcked;
1068-
ASSERT_EQ(Cubic->CongestionWindow, NewWindow);
1069-
ASSERT_EQ(Cubic->BytesInFlight, BytesInFlightAfterAck);
1070-
1071-
// Note: CubicCongestionControlUpdateBlockedState is internal, but we can
1072-
// test the logic through GetSendAllowance behavior changes
1073-
// Pacing is NOT enabled, so Allowance = CongestionWindow - BytesInFlight
1074-
uint32_t Allowance = CC->QuicCongestionControlGetSendAllowance(CC, 1000, TRUE);
1075-
1076-
uint32_t ExpectedAllowance = NewWindow - BytesInFlightAfterAck;
1077-
ASSERT_EQ(Allowance, ExpectedAllowance);
1078-
}
1079-
10801029
//
10811030
// Test: Spurious Congestion Event Rollback
10821031
// Scenario: Tests the spurious congestion event handling. When a congestion event
@@ -1242,68 +1191,50 @@ TEST_F(CubicTest, HyStart_InitialStateVerification)
12421191
//
12431192
// Test: HyStart++ NOT_STARTED → DONE via Loss
12441193
// Scenario: Tests direct transition from NOT_STARTED to DONE when packet loss
1245-
// occurs before HyStart++ detection logic activates. This is the most common
1246-
// path when network conditions cause loss during initial slow start.
1194+
// occurs before HyStart++ detection logic activates. Window reduction mechanics
1195+
// are verified by OnDataLost_WindowReduction; this test focuses on state transition.
12471196
//
12481197
TEST_F(CubicTest, HyStart_NotStartedToDone_ViaLoss)
12491198
{
12501199
InitializeDefaultWithRtt(/*WindowPackets = */ 20, /*HyStart = */ true);
12511200
// Precondition: Verify in NOT_STARTED state
12521201
ASSERT_EQ(Cubic->HyStartState, HYSTART_NOT_STARTED);
12531202

1254-
uint32_t WindowBeforeLoss = Cubic->CongestionWindow;
1255-
12561203
// Send data to have bytes in flight
12571204
CC->QuicCongestionControlOnDataSent(CC, 8000);
12581205
Connection.Send.NextPacketNumber = 10;
12591206

1260-
// Trigger loss event while still in NOT_STARTED
12611207
QUIC_LOSS_EVENT LossEvent = MakeLossEvent(2400, 5, 10);
1262-
12631208
CC->QuicCongestionControlOnDataLost(CC, &LossEvent);
12641209

1265-
// Postcondition: Should transition directly to DONE
1266-
// After loss: Window = InitialWindow * 7 / 10, SSThresh = Window
1267-
uint32_t ExpectedWindow = WindowBeforeLoss * 7 / 10;
12681210
ASSERT_EQ(Cubic->HyStartState, HYSTART_DONE);
1269-
ASSERT_TRUE(Cubic->IsInRecovery);
1270-
ASSERT_TRUE(Cubic->HasHadCongestionEvent);
1271-
ASSERT_EQ(Cubic->CWndSlowStartGrowthDivisor, 1u); // Reset to normal
1272-
ASSERT_EQ(Cubic->CongestionWindow, ExpectedWindow);
1273-
ASSERT_EQ(Cubic->SlowStartThreshold, ExpectedWindow);
1211+
ASSERT_EQ(Cubic->CWndSlowStartGrowthDivisor, 1u);
12741212
}
12751213

12761214
//
12771215
// Test: HyStart++ NOT_STARTED → DONE via ECN
12781216
// Scenario: Tests direct transition from NOT_STARTED to DONE when ECN marking
1279-
// is received, indicating congestion before HyStart++ activates.
1217+
// is received. Window reduction mechanics are verified by OnEcn_CongestionSignal;
1218+
// this test focuses on state transition.
12801219
//
12811220
TEST_F(CubicTest, HyStart_NotStartedToDone_ViaECN)
12821221
{
12831222
InitializeDefaultWithRtt(/*WindowPackets = */ 20, /*HyStart = */ true);
12841223
// Precondition: Verify in NOT_STARTED state
12851224
ASSERT_EQ(Cubic->HyStartState, HYSTART_NOT_STARTED);
1286-
uint32_t WindowBeforeECN = Cubic->CongestionWindow;
12871225

12881226
// Send data
12891227
CC->QuicCongestionControlOnDataSent(CC, 8000);
12901228
Connection.Send.NextPacketNumber = 15;
12911229

1292-
// Trigger ECN event
12931230
QUIC_ECN_EVENT EcnEvent{};
12941231
EcnEvent.LargestPacketNumberAcked = 10;
12951232
EcnEvent.LargestSentPacketNumber = 15;
12961233

12971234
CC->QuicCongestionControlOnEcn(CC, &EcnEvent);
12981235

1299-
// Postcondition: Should transition directly to DONE
1300-
// After ECN: Window = InitialWindow * 7 / 10 (β = 0.7)
1301-
uint32_t ExpectedWindow = WindowBeforeECN * 7 / 10;
13021236
ASSERT_EQ(Cubic->HyStartState, HYSTART_DONE);
1303-
ASSERT_TRUE(Cubic->IsInRecovery);
1304-
ASSERT_TRUE(Cubic->HasHadCongestionEvent);
13051237
ASSERT_EQ(Cubic->CWndSlowStartGrowthDivisor, 1u);
1306-
ASSERT_EQ(Cubic->CongestionWindow, ExpectedWindow);
13071238
}
13081239

13091240
//
@@ -1513,61 +1444,6 @@ TEST_F(CubicTest, HyStart_StateInvariant_GrowthDivisor)
15131444
ASSERT_EQ(Cubic->CWndSlowStartGrowthDivisor, 1u);
15141445
}
15151446

1516-
//
1517-
// Test: HyStart++ Multiple Congestion Events - State Stability
1518-
// Scenario: Tests that multiple congestion events keep the state in DONE and
1519-
// don't cause state corruption. Each event should trigger recovery logic but
1520-
// state should remain DONE.
1521-
//
1522-
TEST_F(CubicTest, HyStart_MultipleCongestionEvents_StateStability)
1523-
{
1524-
InitializeDefaultWithRtt(/*WindowPackets = */ 30, /*HyStart = */ true);
1525-
const uint16_t DatagramPayloadLength = QuicPathGetDatagramPayloadSize(&Connection.Paths[0]);
1526-
uint32_t InitialWindow = DatagramPayloadLength * Settings.InitialWindowPackets;
1527-
1528-
// First congestion event: NOT_STARTED → DONE
1529-
CC->QuicCongestionControlOnDataSent(CC, 8000);
1530-
Connection.Send.NextPacketNumber = 10;
1531-
1532-
QUIC_LOSS_EVENT FirstLoss = MakeLossEvent(2400, 5, 10);
1533-
1534-
CC->QuicCongestionControlOnDataLost(CC, &FirstLoss);
1535-
1536-
ASSERT_EQ(Cubic->HyStartState, HYSTART_DONE);
1537-
// After first loss: Window = InitialWindow * 7 / 10
1538-
uint32_t WindowAfterFirst = InitialWindow * 7 / 10;
1539-
ASSERT_EQ(Cubic->CongestionWindow, WindowAfterFirst);
1540-
1541-
// Exit recovery
1542-
Connection.Send.NextPacketNumber = 20;
1543-
QUIC_ACK_EVENT RecoveryExitAck = MakeAckEvent(1100000, 20, 25, 1200, 50000, 48000);
1544-
1545-
CC->QuicCongestionControlOnDataAcknowledged(CC, &RecoveryExitAck);
1546-
1547-
// Second congestion event: DONE → DONE (should remain)
1548-
CC->QuicCongestionControlOnDataSent(CC, 5000);
1549-
Connection.Send.NextPacketNumber = 30;
1550-
1551-
QUIC_LOSS_EVENT SecondLoss = MakeLossEvent(1800, 28, 30);
1552-
1553-
CC->QuicCongestionControlOnDataLost(CC, &SecondLoss);
1554-
1555-
ASSERT_EQ(Cubic->HyStartState, HYSTART_DONE); // Still DONE
1556-
// After second loss: Window = WindowAfterFirst * 7 / 10
1557-
uint32_t WindowAfterSecond = WindowAfterFirst * 7 / 10;
1558-
ASSERT_EQ(Cubic->CongestionWindow, WindowAfterSecond);
1559-
1560-
// Third congestion event via ECN: DONE → DONE
1561-
Connection.Send.NextPacketNumber = 40;
1562-
QUIC_ECN_EVENT EcnEvent{};
1563-
EcnEvent.LargestPacketNumberAcked = 35;
1564-
EcnEvent.LargestSentPacketNumber = 40;
1565-
1566-
CC->QuicCongestionControlOnEcn(CC, &EcnEvent);
1567-
1568-
ASSERT_EQ(Cubic->HyStartState, HYSTART_DONE); // Still DONE
1569-
}
1570-
15711447
//
15721448
// Test: HyStart++ Recovery Exit with State Persistence
15731449
// Scenario: When exiting recovery (IsInRecovery: TRUE → FALSE), the HyStart
@@ -2243,6 +2119,8 @@ TEST_F(CubicTest, Recovery_DoubleLossNoDoubleReduction)
22432119
ASSERT_EQ(Cubic->BytesInFlight, BytesInFlightAfterFirstLoss - SecondLostBytes);
22442120
}
22452121

2122+
//
2123+
// Test: Pacing LastSendAllowance Carryover
22462124
// Scenario: When pacing is enabled and GetSendAllowance is called multiple times
22472125
// without sending data, the unused allowance (LastSendAllowance) carries over
22482126
// and accumulates into the next call's result.

0 commit comments

Comments
 (0)