@@ -371,6 +371,7 @@ impl Recovery {
371371/// algorithms.
372372#[ derive( Debug , Copy , Clone , PartialEq , Eq ) ]
373373#[ repr( C ) ]
374+ #[ non_exhaustive]
374375pub enum CongestionControlAlgorithm {
375376 /// Reno congestion control algorithm. `reno` in a string form.
376377 Reno = 0 ,
@@ -379,6 +380,18 @@ pub enum CongestionControlAlgorithm {
379380 /// BBRv2 congestion control algorithm implementation from gcongestion
380381 /// branch. `bbr2_gcongestion` in a string form.
381382 Bbr2Gcongestion = 4 ,
383+ /// Keeps the congestion window at `usize::MAX` instead of reducing send
384+ /// capacity in response to congestion. ACK processing, RTT estimation,
385+ /// loss detection, and recovery remain enabled. This provides no
386+ /// congestion-window protection and makes optimistic-ACK probes extremely
387+ /// infrequent.
388+ /// `congestion_window_unchecked` in string form.
389+ #[ cfg( feature = "congestion_window_unchecked_available" ) ]
390+ #[ cfg_attr(
391+ docsrs,
392+ doc( cfg( feature = "congestion_window_unchecked_available" ) )
393+ ) ]
394+ CongestionWindowUnchecked = 5 ,
382395}
383396
384397impl FromStr for CongestionControlAlgorithm {
@@ -394,6 +407,9 @@ impl FromStr for CongestionControlAlgorithm {
394407 "bbr" => Ok ( CongestionControlAlgorithm :: Bbr2Gcongestion ) ,
395408 "bbr2" => Ok ( CongestionControlAlgorithm :: Bbr2Gcongestion ) ,
396409 "bbr2_gcongestion" => Ok ( CongestionControlAlgorithm :: Bbr2Gcongestion ) ,
410+ #[ cfg( feature = "congestion_window_unchecked_available" ) ]
411+ "congestion_window_unchecked" =>
412+ Ok ( CongestionControlAlgorithm :: CongestionWindowUnchecked ) ,
397413 _ => Err ( crate :: Error :: CongestionControl ) ,
398414 }
399415 }
@@ -953,10 +969,94 @@ mod tests {
953969 ) ;
954970 }
955971
972+ #[ cfg( feature = "congestion_window_unchecked_available" ) ]
973+ fn congestion_window_unchecked_recovery ( ) -> Recovery {
974+ let mut config = Config :: new ( crate :: PROTOCOL_VERSION )
975+ . expect ( "configuration should be valid" ) ;
976+ config
977+ . set_cc_algorithm_name ( "congestion_window_unchecked" )
978+ . expect ( "congestion_window_unchecked should be available" ) ;
979+ Recovery :: new ( & config)
980+ }
981+
982+ #[ cfg( feature = "congestion_window_unchecked_available" ) ]
983+ #[ test]
984+ fn congestion_window_unchecked_keeps_the_send_window_open ( ) {
985+ let mut recovery = congestion_window_unchecked_recovery ( ) ;
986+ let now = Instant :: now ( ) ;
987+ recovery. on_packet_sent (
988+ test_utils:: helper_packet_sent ( 0 , now, 1_200 ) ,
989+ packet:: Epoch :: Application ,
990+ HandshakeStatus :: default ( ) ,
991+ now,
992+ "" ,
993+ ) ;
994+
995+ assert_eq ! ( recovery. cwnd( ) , usize :: MAX ) ;
996+ assert_eq ! ( recovery. bytes_in_flight( ) , 1_200 ) ;
997+ assert_eq ! ( recovery. cwnd_available( ) , usize :: MAX - 1_200 ) ;
998+ }
999+
1000+ #[ cfg( feature = "congestion_window_unchecked_available" ) ]
1001+ #[ test]
1002+ fn congestion_window_unchecked_retains_recovery_accounting ( ) {
1003+ let mut recovery = congestion_window_unchecked_recovery ( ) ;
1004+ let now = Instant :: now ( ) ;
1005+ recovery. on_packet_sent (
1006+ test_utils:: helper_packet_sent ( 0 , now, 1_200 ) ,
1007+ packet:: Epoch :: Application ,
1008+ HandshakeStatus :: default ( ) ,
1009+ now,
1010+ "" ,
1011+ ) ;
1012+
1013+ let mut acked = RangeSet :: default ( ) ;
1014+ acked. insert ( 0 ..1 ) ;
1015+ let outcome = recovery
1016+ . on_ack_received (
1017+ & acked,
1018+ 0 ,
1019+ packet:: Epoch :: Application ,
1020+ HandshakeStatus :: default ( ) ,
1021+ now + Duration :: from_millis ( 10 ) ,
1022+ None ,
1023+ "" ,
1024+ )
1025+ . expect ( "ACK should be valid" ) ;
1026+ assert_eq ! ( outcome. acked_bytes, 1_200 ) ;
1027+ assert_eq ! ( recovery. bytes_in_flight( ) , 0 ) ;
1028+ assert_eq ! ( recovery. rtt( ) , Duration :: from_millis( 10 ) ) ;
1029+ assert_eq ! ( recovery. min_rtt( ) , Some ( Duration :: from_millis( 10 ) ) ) ;
1030+
1031+ let mut recovery = congestion_window_unchecked_recovery ( ) ;
1032+ recovery. on_packet_sent (
1033+ test_utils:: helper_packet_sent (
1034+ 0 ,
1035+ now + Duration :: from_secs ( 2 ) ,
1036+ 1_200 ,
1037+ ) ,
1038+ packet:: Epoch :: Initial ,
1039+ HandshakeStatus :: default ( ) ,
1040+ now + Duration :: from_secs ( 2 ) ,
1041+ "" ,
1042+ ) ;
1043+ recovery. on_pkt_num_space_discarded (
1044+ packet:: Epoch :: Initial ,
1045+ HandshakeStatus :: default ( ) ,
1046+ now + Duration :: from_secs ( 3 ) ,
1047+ ) ;
1048+ assert_eq ! ( recovery. bytes_in_flight( ) , 0 ) ;
1049+ }
1050+
9561051 #[ rstest]
957- fn loss_on_pto (
958- #[ values( "reno" , "cubic" , "bbr2_gcongestion" ) ] cc_algorithm_name : & str ,
959- ) {
1052+ #[ case:: reno( "reno" ) ]
1053+ #[ case:: cubic( "cubic" ) ]
1054+ #[ case:: bbr2_gcongestion( "bbr2_gcongestion" ) ]
1055+ #[ cfg_attr(
1056+ feature = "congestion_window_unchecked_available" ,
1057+ case:: congestion_window_unchecked( "congestion_window_unchecked" )
1058+ ) ]
1059+ fn loss_on_pto ( #[ case] cc_algorithm_name : & str ) {
9601060 let mut cfg = Config :: new ( crate :: PROTOCOL_VERSION ) . unwrap ( ) ;
9611061 assert_eq ! ( cfg. set_cc_algorithm_name( cc_algorithm_name) , Ok ( ( ) ) ) ;
9621062
@@ -1231,7 +1331,10 @@ mod tests {
12311331 ) ;
12321332
12331333 assert_eq ! ( r. sent_packets_len( packet:: Epoch :: Application ) , 0 ) ;
1234- if cc_algorithm_name == "reno" || cc_algorithm_name == "cubic" {
1334+ if matches ! (
1335+ cc_algorithm_name,
1336+ "reno" | "cubic" | "congestion_window_unchecked"
1337+ ) {
12351338 assert ! ( r. startup_exit( ) . is_some( ) ) ;
12361339 assert_eq ! ( r. startup_exit( ) . unwrap( ) . reason, StartupExitReason :: Loss ) ;
12371340 } else {
@@ -1240,9 +1343,14 @@ mod tests {
12401343 }
12411344
12421345 #[ rstest]
1243- fn loss_on_timer (
1244- #[ values( "reno" , "cubic" , "bbr2_gcongestion" ) ] cc_algorithm_name : & str ,
1245- ) {
1346+ #[ case:: reno( "reno" ) ]
1347+ #[ case:: cubic( "cubic" ) ]
1348+ #[ case:: bbr2_gcongestion( "bbr2_gcongestion" ) ]
1349+ #[ cfg_attr(
1350+ feature = "congestion_window_unchecked_available" ,
1351+ case:: congestion_window_unchecked( "congestion_window_unchecked" )
1352+ ) ]
1353+ fn loss_on_timer ( #[ case] cc_algorithm_name : & str ) {
12461354 let mut cfg = Config :: new ( crate :: PROTOCOL_VERSION ) . unwrap ( ) ;
12471355 assert_eq ! ( cfg. set_cc_algorithm_name( cc_algorithm_name) , Ok ( ( ) ) ) ;
12481356
@@ -1427,7 +1535,10 @@ mod tests {
14271535 ) ;
14281536
14291537 assert_eq ! ( r. sent_packets_len( packet:: Epoch :: Application ) , 0 ) ;
1430- if cc_algorithm_name == "reno" || cc_algorithm_name == "cubic" {
1538+ if matches ! (
1539+ cc_algorithm_name,
1540+ "reno" | "cubic" | "congestion_window_unchecked"
1541+ ) {
14311542 assert ! ( r. startup_exit( ) . is_some( ) ) ;
14321543 assert_eq ! ( r. startup_exit( ) . unwrap( ) . reason, StartupExitReason :: Loss ) ;
14331544 } else {
@@ -1436,9 +1547,14 @@ mod tests {
14361547 }
14371548
14381549 #[ rstest]
1439- fn loss_on_reordering (
1440- #[ values( "reno" , "cubic" , "bbr2_gcongestion" ) ] cc_algorithm_name : & str ,
1441- ) {
1550+ #[ case:: reno( "reno" ) ]
1551+ #[ case:: cubic( "cubic" ) ]
1552+ #[ case:: bbr2_gcongestion( "bbr2_gcongestion" ) ]
1553+ #[ cfg_attr(
1554+ feature = "congestion_window_unchecked_available" ,
1555+ case:: congestion_window_unchecked( "congestion_window_unchecked" )
1556+ ) ]
1557+ fn loss_on_reordering ( #[ case] cc_algorithm_name : & str ) {
14421558 let mut cfg = Config :: new ( crate :: PROTOCOL_VERSION ) . unwrap ( ) ;
14431559 assert_eq ! ( cfg. set_cc_algorithm_name( cc_algorithm_name) , Ok ( ( ) ) ) ;
14441560
@@ -1543,7 +1659,10 @@ mod tests {
15431659 ) ;
15441660 assert_eq ! ( r. sent_packets_len( packet:: Epoch :: Application ) , 0 ) ;
15451661
1546- if cc_algorithm_name == "reno" || cc_algorithm_name == "cubic" {
1662+ if matches ! (
1663+ cc_algorithm_name,
1664+ "reno" | "cubic" | "congestion_window_unchecked"
1665+ ) {
15471666 assert ! ( r. startup_exit( ) . is_some( ) ) ;
15481667 assert_eq ! ( r. startup_exit( ) . unwrap( ) . reason, StartupExitReason :: Loss ) ;
15491668 } else {
0 commit comments