@@ -27,7 +27,7 @@ pub enum Outcome {
2727 /// Not enough data to run SEARCH evaluation (expected early in the connection or after reset).
2828 WarmingUp ,
2929 /// Can't run SEARCH evaluation because RTT inflated past the point where there isn't enough
30- /// data to look back one RTT. Provides the amount of bins that SEARCH tried to look back.
30+ /// data to look back one RTT. Provides the number of bins that SEARCH tried to look back.
3131 RttInflated ( usize ) ,
3232 /// Haven't sent data for the last RTT thus can't evaluate.
3333 ZeroSent ,
@@ -56,7 +56,7 @@ pub struct Search {
5656 /// Tracking amount of sent bytes on this connection. Is incremented on every sent packet.
5757 sent_bytes : usize ,
5858 /// The RTT used to initialize SEARCH (set in [`Self::initialize`]).
59- initial_rtt : Duration ,
59+ initial_rtt : Option < Duration > ,
6060}
6161
6262impl Display for Search {
@@ -98,7 +98,7 @@ impl Search {
9898 bin_duration : Duration :: from_millis ( 0 ) ,
9999 acked_bytes : 0 ,
100100 sent_bytes : 0 ,
101- initial_rtt : Duration :: ZERO ,
101+ initial_rtt : None ,
102102 }
103103 }
104104
@@ -108,7 +108,7 @@ impl Search {
108108 reason = "casting small constant usize to u32 for use in Duration::div"
109109 ) ]
110110 fn initialize ( & mut self , initial_rtt : Duration , now : Instant ) {
111- self . initial_rtt = initial_rtt;
111+ self . initial_rtt = Some ( initial_rtt) ;
112112 // BIN_DURATION = WINDOW_SIZE / W = initial_rtt * WINDOW_SIZE_FACTOR / W
113113 self . bin_duration =
114114 initial_rtt * Self :: WINDOW_SIZE_FACTOR / Self :: SCALE as u32 / Self :: W as u32 ;
@@ -165,12 +165,8 @@ impl Search {
165165 Self :: W
166166 ) ;
167167 cc_stats. search_reset . count += 1 ;
168- cc_stats. search_reset . max_passed_bins = Some (
169- cc_stats
170- . search_reset
171- . max_passed_bins
172- . map_or ( passed_bins, |c| c. max ( passed_bins) ) ,
173- ) ;
168+ cc_stats. search_reset . max_passed_bins =
169+ cc_stats. search_reset . max_passed_bins . max ( Some ( passed_bins) ) ;
174170 self . reset ( ) ;
175171 return None ;
176172 }
@@ -291,39 +287,44 @@ impl Search {
291287 Outcome :: Exit ( curr_cwnd)
292288 }
293289
290+ /// Converts an RTT duration to the number of bins it spans (rounded up).
291+ fn rtt_to_bins ( & self , rtt : Duration ) -> usize {
292+ let rtt_ns = u64:: try_from ( rtt. as_nanos ( ) ) . unwrap_or ( u64:: MAX ) ;
293+ let bin_ns = u64:: try_from ( self . bin_duration . as_nanos ( ) ) . unwrap_or ( u64:: MAX ) ;
294+ usize:: try_from ( rtt_ns. div_ceil ( bin_ns) ) . unwrap_or ( usize:: MAX )
295+ }
296+
294297 /// SEARCH suggests a drain-phase to slowly converge towards a BDP estimate. We're currently not
295298 /// implementing this, but do record the calculated targets for evaluation in this function.
296299 ///
297300 /// The draft specifies using the initial rtt to approximate an 'empty-buffer BDP'.
298301 ///
299302 /// In addition also record an estimate using the current rtt to approximate BDP with full
300303 /// buffers. This should estimate the upper end of the CUBIC curve during congestion avoidance.
301- #[ expect(
302- clippy:: cast_possible_truncation,
303- reason = "casting small u128 division result to usize"
304- ) ]
305- const fn record_target_cwnd_estimates (
304+ fn record_target_cwnd_estimates (
306305 & self ,
307306 curr_idx : usize ,
308307 rtt : Duration ,
309308 cc_stats : & mut CongestionControlStats ,
310309 ) {
311- let initial_rtt_bins = self
312- . initial_rtt
313- . as_nanos ( )
314- . div_ceil ( self . bin_duration . as_nanos ( ) ) as usize ;
310+ // Only compute estimates if we have an initial RTT. This is a pure precaution, since we
311+ // should always have an initial RTT before this method is called.
312+ let Some ( initial_rtt) = self . initial_rtt else {
313+ return ;
314+ } ;
315+ let initial_rtt_bins = self . rtt_to_bins ( initial_rtt) ;
315316 let initial_rtt_cong_idx = curr_idx. saturating_sub ( initial_rtt_bins) ;
316317 let empty_buffer_target = self . compute_delv ( initial_rtt_cong_idx, curr_idx) ;
317318
318- cc_stats. search_empty_buffer_target = Some ( empty_buffer_target as usize ) ;
319+ cc_stats. search_empty_buffer_target = Some ( empty_buffer_target) ;
319320
320321 // Only compute full_buffer_target when the current RTT fits within the acked_bins
321322 // circular buffer (W + 1 slots). Beyond that, modulo indexing reads overwritten entries.
322- let current_rtt_bins = rtt . as_nanos ( ) . div_ceil ( self . bin_duration . as_nanos ( ) ) as usize ;
323+ let current_rtt_bins = self . rtt_to_bins ( rtt ) ;
323324 if current_rtt_bins <= Self :: W {
324325 let current_rtt_cong_idx = curr_idx. saturating_sub ( current_rtt_bins) ;
325- cc_stats . search_full_buffer_target =
326- Some ( self . compute_delv ( current_rtt_cong_idx , curr_idx ) as usize ) ;
326+ let full_buffer_target = self . compute_delv ( current_rtt_cong_idx , curr_idx ) ;
327+ cc_stats . search_full_buffer_target = Some ( full_buffer_target ) ;
327328 }
328329 }
329330
@@ -391,8 +392,8 @@ impl SlowStart for Search {
391392 // Guard on the stats fields so post-reset ACKs don't overwrite the initial samples.
392393 if cc_stats. search_first_rtt . is_none ( ) {
393394 cc_stats. search_first_rtt = Some ( rtt) ;
394- } else if cc_stats . search_second_rtt . is_none ( ) {
395- cc_stats. search_second_rtt = Some ( rtt) ;
395+ } else {
396+ cc_stats. search_second_rtt . get_or_insert ( rtt) ;
396397 }
397398
398399 // Initialize on first ACK.
@@ -430,19 +431,13 @@ impl SlowStart for Search {
430431 Some ( cwnd)
431432 }
432433 Outcome :: RttInflated ( lookback_bins) => {
433- cc_stats. search_lookback_bins_needed = Some (
434- cc_stats
435- . search_lookback_bins_needed
436- . map_or ( lookback_bins, |c| c. max ( lookback_bins) ) ,
437- ) ;
434+ cc_stats. search_lookback_bins_needed = cc_stats
435+ . search_lookback_bins_needed
436+ . max ( Some ( lookback_bins) ) ;
438437 None
439438 }
440439 Outcome :: Continue ( norm_diff) => {
441- cc_stats. search_max_norm_diff = Some (
442- cc_stats
443- . search_max_norm_diff
444- . map_or ( norm_diff, |c| c. max ( norm_diff) ) ,
445- ) ;
440+ cc_stats. search_max_norm_diff = cc_stats. search_max_norm_diff . max ( Some ( norm_diff) ) ;
446441 None
447442 }
448443 Outcome :: ZeroSent => {
0 commit comments