Add missing MaybeUpdateAckTimeout for ack-eliciting frames.

Protected by FLAGS_quic_reloadable_flag_quic_add_missing_update_ack_timeout.

PiperOrigin-RevId: 384760052
diff --git a/quic/core/frames/quic_new_connection_id_frame.h b/quic/core/frames/quic_new_connection_id_frame.h
index 9c3d205..235d2af 100644
--- a/quic/core/frames/quic_new_connection_id_frame.h
+++ b/quic/core/frames/quic_new_connection_id_frame.h
@@ -32,7 +32,7 @@
   QuicConnectionId connection_id = EmptyQuicConnectionId();
   QuicConnectionIdSequenceNumber sequence_number = 0;
   StatelessResetToken stateless_reset_token;
-  uint64_t retire_prior_to;
+  uint64_t retire_prior_to = 0;
 };
 
 }  // namespace quic
diff --git a/quic/core/frames/quic_streams_blocked_frame.h b/quic/core/frames/quic_streams_blocked_frame.h
index 94676fb..2d73890 100644
--- a/quic/core/frames/quic_streams_blocked_frame.h
+++ b/quic/core/frames/quic_streams_blocked_frame.h
@@ -35,7 +35,7 @@
   QuicControlFrameId control_frame_id = kInvalidControlFrameId;
 
   // The number of streams that the sender wishes to exceed
-  QuicStreamCount stream_count;
+  QuicStreamCount stream_count = 0;
 
   // Whether uni- or bi-directional streams
   bool unidirectional = false;
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index 9defbe3..96428a3 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -378,6 +378,10 @@
     QUIC_RELOADABLE_FLAG_COUNT(quic_use_encryption_level_context);
   }
 
+  if (add_missing_update_ack_timeout_) {
+    QUIC_RELOADABLE_FLAG_COUNT(quic_add_missing_update_ack_timeout);
+  }
+
   support_multiple_connection_ids_ =
       version().HasIetfQuicFrames() &&
       GetQuicRestartFlag(quic_time_wait_list_support_multiple_cid_v2) &&
@@ -1399,6 +1403,9 @@
                     ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
     return false;
   }
+  // TODO(fayang): Consider moving UpdatePacketContent and
+  // MaybeUpdateAckTimeout to a stand-alone function instead of calling them for
+  // all frames.
   MaybeUpdateAckTimeout();
   visitor_->OnStreamFrame(frame);
   stats_.stream_bytes_received += frame.data_length;
@@ -1707,7 +1714,9 @@
   QUIC_DLOG(INFO) << ENDPOINT << "STOP_SENDING frame received for stream: "
                   << frame.stream_id
                   << " with error: " << frame.ietf_error_code;
-
+  if (add_missing_update_ack_timeout_) {
+    MaybeUpdateAckTimeout();
+  }
   visitor_->OnStopSendingFrame(frame);
   return connected_;
 }
@@ -1931,6 +1940,9 @@
   if (debug_visitor_ != nullptr) {
     debug_visitor_->OnMaxStreamsFrame(frame);
   }
+  if (add_missing_update_ack_timeout_) {
+    MaybeUpdateAckTimeout();
+  }
   return visitor_->OnMaxStreamsFrame(frame) && connected_;
 }
 
@@ -1947,6 +1959,9 @@
   if (debug_visitor_ != nullptr) {
     debug_visitor_->OnStreamsBlockedFrame(frame);
   }
+  if (add_missing_update_ack_timeout_) {
+    MaybeUpdateAckTimeout();
+  }
   return visitor_->OnStreamsBlockedFrame(frame) && connected_;
 }
 
@@ -2077,6 +2092,9 @@
     OnClientConnectionIdAvailable();
   }
   QUIC_RELOADABLE_FLAG_COUNT_N(quic_connection_support_multiple_cids_v4, 1, 2);
+  if (add_missing_update_ack_timeout_) {
+    MaybeUpdateAckTimeout();
+  }
   return true;
 }
 
@@ -2137,6 +2155,9 @@
   QUIC_RELOADABLE_FLAG_COUNT_N(quic_connection_support_multiple_cids_v4, 2, 2);
   // Count successfully received RETIRE_CONNECTION_ID frames.
   QUIC_RELOADABLE_FLAG_COUNT_N(quic_connection_migration_use_new_cid_v2, 5, 6);
+  if (add_missing_update_ack_timeout_) {
+    MaybeUpdateAckTimeout();
+  }
   return true;
 }
 
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h
index 9a02675..6e989ba 100644
--- a/quic/core/quic_connection.h
+++ b/quic/core/quic_connection.h
@@ -2283,6 +2283,9 @@
   const bool reset_per_packet_state_for_undecryptable_packets_ =
       GetQuicReloadableFlag(
           quic_reset_per_packet_state_for_undecryptable_packets);
+
+  const bool add_missing_update_ack_timeout_ =
+      GetQuicReloadableFlag(quic_add_missing_update_ack_timeout);
 };
 
 }  // namespace quic
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index 5e6c8ee..f16604b 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -23,6 +23,7 @@
 #include "quic/core/frames/quic_connection_close_frame.h"
 #include "quic/core/frames/quic_new_connection_id_frame.h"
 #include "quic/core/frames/quic_path_response_frame.h"
+#include "quic/core/frames/quic_rst_stream_frame.h"
 #include "quic/core/quic_connection_id.h"
 #include "quic/core/quic_constants.h"
 #include "quic/core/quic_error_codes.h"
@@ -12079,6 +12080,7 @@
   new_writer.BlockOnNextWrite();
   EXPECT_CALL(visitor_, OnWriteBlocked()).Times(0);
   EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _))
+      .Times(AtLeast(1))
       .WillOnce(Invoke([&]() {
         // Even though the socket is blocked, the PATH_CHALLENGE should still be
         // treated as sent.
@@ -12099,7 +12101,11 @@
   new_writer.SetWritable();
   // Write event on the default socket shouldn't make any difference.
   connection_.OnCanWrite();
-  EXPECT_EQ(0u, writer_->packets_write_attempts());
+  if (GetQuicReloadableFlag(quic_add_missing_update_ack_timeout)) {
+    EXPECT_EQ(1u, writer_->packets_write_attempts());
+  } else {
+    EXPECT_EQ(0u, writer_->packets_write_attempts());
+  }
   EXPECT_EQ(1u, new_writer.packets_write_attempts());
 }
 
@@ -15206,6 +15212,149 @@
   EXPECT_EQ(0x01010101u, writer_->final_bytes_of_last_packet());
 }
 
+TEST_P(QuicConnectionTest, AckElicitingFrames) {
+  if (!version().HasIetfQuicFrames() ||
+      !connection_.support_multiple_connection_ids() ||
+      !GetQuicReloadableFlag(quic_add_missing_update_ack_timeout)) {
+    return;
+  }
+  EXPECT_CALL(visitor_, OnRstStream(_));
+  EXPECT_CALL(visitor_, OnWindowUpdateFrame(_));
+  EXPECT_CALL(visitor_, OnBlockedFrame(_));
+  EXPECT_CALL(visitor_, OnHandshakeDoneReceived());
+  EXPECT_CALL(visitor_, OnStreamFrame(_));
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(_, _, _, _, _));
+  EXPECT_CALL(visitor_, OnMaxStreamsFrame(_));
+  EXPECT_CALL(visitor_, OnStreamsBlockedFrame(_));
+  EXPECT_CALL(visitor_, OnStopSendingFrame(_));
+  EXPECT_CALL(visitor_, OnMessageReceived(""));
+  EXPECT_CALL(visitor_, OnNewTokenReceived(""));
+
+  connection_.CreateConnectionIdManager();
+  connection_.set_can_receive_ack_frequency_frame();
+
+  QuicAckFrame ack_frame = InitAckFrame(1);
+  QuicRstStreamFrame rst_stream_frame;
+  QuicWindowUpdateFrame window_update_frame;
+  QuicPathChallengeFrame path_challenge_frame;
+  QuicStopSendingFrame stop_sending_frame;
+  QuicNewConnectionIdFrame new_connection_id_frame;
+  QuicPathResponseFrame path_response_frame;
+  QuicMessageFrame message_frame;
+  QuicNewTokenFrame new_token_frame;
+  QuicAckFrequencyFrame ack_frequency_frame;
+  QuicBlockedFrame blocked_frame;
+  size_t packet_number = 1;
+
+  connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+
+  for (uint8_t i = 0; i < NUM_FRAME_TYPES; ++i) {
+    QuicFrameType frame_type = static_cast<QuicFrameType>(i);
+    bool skipped = false;
+    QuicFrame frame;
+    QuicFrames frames;
+    // Add some padding to fullfill the min size requirement of header
+    // protection.
+    frames.push_back(QuicFrame(QuicPaddingFrame(10)));
+    switch (frame_type) {
+      case PADDING_FRAME:
+        frame = QuicFrame(QuicPaddingFrame(10));
+        break;
+      case MTU_DISCOVERY_FRAME:
+        frame = QuicFrame(QuicMtuDiscoveryFrame());
+        break;
+      case PING_FRAME:
+        frame = QuicFrame(QuicPingFrame());
+        break;
+      case MAX_STREAMS_FRAME:
+        frame = QuicFrame(QuicMaxStreamsFrame());
+        break;
+      case STOP_WAITING_FRAME:
+        // Not supported.
+        skipped = true;
+        break;
+      case STREAMS_BLOCKED_FRAME:
+        frame = QuicFrame(QuicStreamsBlockedFrame());
+        break;
+      case STREAM_FRAME:
+        frame = QuicFrame(QuicStreamFrame());
+        break;
+      case HANDSHAKE_DONE_FRAME:
+        frame = QuicFrame(QuicHandshakeDoneFrame());
+        break;
+      case ACK_FRAME:
+        frame = QuicFrame(&ack_frame);
+        break;
+      case RST_STREAM_FRAME:
+        frame = QuicFrame(&rst_stream_frame);
+        break;
+      case CONNECTION_CLOSE_FRAME:
+        // Do not test connection close.
+        skipped = true;
+        break;
+      case GOAWAY_FRAME:
+        // Does not exist in IETF QUIC.
+        skipped = true;
+        break;
+      case BLOCKED_FRAME:
+        frame = QuicFrame(&blocked_frame);
+        break;
+      case WINDOW_UPDATE_FRAME:
+        frame = QuicFrame(&window_update_frame);
+        break;
+      case PATH_CHALLENGE_FRAME:
+        frame = QuicFrame(&path_challenge_frame);
+        break;
+      case STOP_SENDING_FRAME:
+        frame = QuicFrame(&stop_sending_frame);
+        break;
+      case NEW_CONNECTION_ID_FRAME:
+        frame = QuicFrame(&new_connection_id_frame);
+        break;
+      case RETIRE_CONNECTION_ID_FRAME:
+        // TODO(haoyuewang): add test coverage for RETIRE_CONNECTION_ID_FRAME.
+        skipped = true;
+        break;
+      case PATH_RESPONSE_FRAME:
+        frame = QuicFrame(&path_response_frame);
+        break;
+      case MESSAGE_FRAME:
+        frame = QuicFrame(&message_frame);
+        break;
+      case CRYPTO_FRAME:
+        // CRYPTO_FRAME is ack eliciting is covered by other tests.
+        skipped = true;
+        break;
+      case NEW_TOKEN_FRAME:
+        frame = QuicFrame(&new_token_frame);
+        break;
+      case ACK_FREQUENCY_FRAME:
+        frame = QuicFrame(&ack_frequency_frame);
+        break;
+      case NUM_FRAME_TYPES:
+        skipped = true;
+        break;
+    }
+    if (skipped) {
+      continue;
+    }
+    ASSERT_EQ(frame_type, frame.type);
+    frames.push_back(frame);
+    EXPECT_FALSE(connection_.HasPendingAcks());
+    // Process frame.
+    ProcessFramesPacketAtLevel(packet_number++, frames,
+                               ENCRYPTION_FORWARD_SECURE);
+    if (QuicUtils::IsAckElicitingFrame(frame_type)) {
+      ASSERT_TRUE(connection_.HasPendingAcks()) << frame;
+      // Flush ACK.
+      clock_.AdvanceTime(DefaultDelayedAckTime());
+      connection_.GetAckAlarm()->Fire();
+    }
+    EXPECT_FALSE(connection_.HasPendingAcks());
+    ASSERT_TRUE(connection_.connected());
+  }
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h
index 55e7751..8abba28 100644
--- a/quic/core/quic_flags_list.h
+++ b/quic/core/quic_flags_list.h
@@ -27,6 +27,8 @@
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_abort_qpack_on_stream_reset, true)
 // If true, ack frequency frame can be sent from server to client.
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_can_send_ack_frequency, true)
+// If true, add missing MaybeUpdateAckTimeout for ack-eliciting frames.
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_add_missing_update_ack_timeout, true)
 // If true, allow client to enable BBRv2 on server via connection option \'B2ON\'.
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_allow_client_enabled_bbr_v2, false)
 // If true, allow ticket open to be ignored in TlsServerHandshaker. Also fixes TlsServerHandshaker::ResumptionAttempted when handshake hints is used.
diff --git a/quic/core/quic_utils.cc b/quic/core/quic_utils.cc
index 65c9ced..98fbf55 100644
--- a/quic/core/quic_utils.cc
+++ b/quic/core/quic_utils.cc
@@ -695,6 +695,19 @@
 }
 
 // static
+bool QuicUtils::IsAckElicitingFrame(QuicFrameType type) {
+  switch (type) {
+    case PADDING_FRAME:
+    case STOP_WAITING_FRAME:
+    case ACK_FRAME:
+    case CONNECTION_CLOSE_FRAME:
+      return false;
+    default:
+      return true;
+  }
+}
+
+// static
 bool QuicUtils::AreStatelessResetTokensEqual(
     const StatelessResetToken& token1,
     const StatelessResetToken& token2) {
diff --git a/quic/core/quic_utils.h b/quic/core/quic_utils.h
index 6fcc5f2..c335bc0 100644
--- a/quic/core/quic_utils.h
+++ b/quic/core/quic_utils.h
@@ -244,6 +244,9 @@
   // comparison in constant time.
   static bool AreStatelessResetTokensEqual(const StatelessResetToken& token1,
                                            const StatelessResetToken& token2);
+
+  // Return ture if this frame is an ack-eliciting frame.
+  static bool IsAckElicitingFrame(QuicFrameType type);
 };
 
 // Returns true if the specific ID is a valid WebTransport session ID that our