diff --git a/src/socket/tcp.rs b/src/socket/tcp.rs index 267f10729..af6edbe74 100644 --- a/src/socket/tcp.rs +++ b/src/socket/tcp.rs @@ -656,7 +656,6 @@ impl<'a> Socket<'a> { /// Return the current window field value, including scaling according to RFC 1323. /// /// Used in internal calculations as well as packet generation. - /// #[inline] fn scaled_window(&self) -> u16 { cmp::min( @@ -665,6 +664,25 @@ impl<'a> Socket<'a> { ) as u16 } + /// Return the last window field value, including scaling according to RFC 1323. + /// + /// Used in internal calculations as well as packet generation. + /// + /// Unlike `remote_last_win`, we take into account new packets received (but not acknowledged) + /// since the last window update and adjust the window length accordingly. This ensures a fair + /// comparison between the last window length and the new window length we're going to + /// advertise. + #[inline] + fn last_scaled_window(&self) -> Option { + let last_ack = self.remote_last_ack?; + let next_ack = self.remote_seq_no + self.rx_buffer.len(); + + let last_win = (self.remote_last_win as usize) << self.remote_win_shift; + let last_win_adjusted = last_ack + last_win - next_ack; + + Some(cmp::min(last_win_adjusted >> self.remote_win_shift, (1 << 16) - 1) as u16) + } + /// Set the timeout duration. /// /// A socket with a timeout duration set will abort the connection if either of the following @@ -2136,7 +2154,13 @@ impl<'a> Socket<'a> { | State::SynReceived | State::Established | State::FinWait1 - | State::FinWait2 => self.scaled_window() > self.remote_last_win, + | State::FinWait2 => { + let new_win = self.scaled_window(); + new_win > 0 + && self + .last_scaled_window() + .is_some_and(|last_win| new_win / 2 >= last_win) + } _ => false, } } @@ -2202,7 +2226,7 @@ impl<'a> Socket<'a> { } else if self.ack_to_transmit() && self.delayed_ack_expired(cx.now()) { // If we have data to acknowledge, do it. tcp_trace!("outgoing segment will acknowledge"); - } else if self.window_to_update() && self.delayed_ack_expired(cx.now()) { + } else if self.window_to_update() { // If we have window length increase to advertise, do it. tcp_trace!("outgoing segment will update window"); } else if self.state == State::Closed { @@ -2452,8 +2476,11 @@ impl<'a> Socket<'a> { } else if self.seq_to_transmit(cx) { // We have a data or flag packet to transmit. PollAt::Now + } else if self.window_to_update() { + // The receive window has been raised significantly. + PollAt::Now } else { - let want_ack = self.ack_to_transmit() || self.window_to_update(); + let want_ack = self.ack_to_transmit(); let delayed_ack_poll_at = match (want_ack, self.ack_delay_timer) { (false, _) => PollAt::Ingress, @@ -2785,7 +2812,7 @@ mod test { s.local_seq_no = LOCAL_SEQ + 1; s.remote_last_seq = LOCAL_SEQ + 1; s.remote_last_ack = Some(REMOTE_SEQ + 1); - s.remote_last_win = 64; + s.remote_last_win = s.scaled_window(); s } @@ -6325,6 +6352,63 @@ mod test { })); } + #[test] + fn test_window_update_with_delay_ack() { + let mut s = socket_established_with_buffer_sizes(6, 6); + s.ack_delay = Some(Duration::from_millis(10)); + + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"abcdef"[..], + ..SEND_TEMPL + } + ); + + recv_nothing!(s, time 5); + + s.recv(|buffer| { + assert_eq!(&buffer[..2], b"ab"); + (2, ()) + }) + .unwrap(); + recv!( + s, + time 5, + Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 6), + window_len: 2, + ..RECV_TEMPL + }) + ); + + s.recv(|buffer| { + assert_eq!(&buffer[..1], b"c"); + (1, ()) + }) + .unwrap(); + recv_nothing!(s, time 5); + + s.recv(|buffer| { + assert_eq!(&buffer[..1], b"d"); + (1, ()) + }) + .unwrap(); + recv!( + s, + time 5, + Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 6), + window_len: 4, + ..RECV_TEMPL + }) + ); + } + #[test] fn test_fill_peer_window() { let mut s = socket_established();