IMMEDIATE_ACK frame processing.

Protected by FLAGS_quic_reloadable_flag_quic_receive_ack_frequency.

PiperOrigin-RevId: 718051494
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc
index 6a12e48..11e859c 100644
--- a/quiche/quic/core/quic_connection.cc
+++ b/quiche/quic/core/quic_connection.cc
@@ -2121,7 +2121,16 @@
     QUIC_LOG_EVERY_N_SEC(ERROR, 120) << "Got unexpected ImmediateAck Frame.";
     return false;
   }
-  QUIC_RELOADABLE_FLAG_COUNT_N(quic_receive_ack_frequency, 1, 1);
+  QUIC_RELOADABLE_FLAG_COUNT_N(quic_receive_ack_frequency, 1, 2);
+  if (last_received_packet_info_.decrypted_level == ENCRYPTION_FORWARD_SECURE) {
+    uber_received_packet_manager_.OnImmediateAckFrame();
+  } else {
+    QUIC_LOG_EVERY_N_SEC(ERROR, 120)
+        << "Got ImmediateAckFrame at EncryptionLevel "
+        << EncryptionLevelToString(last_received_packet_info_.decrypted_level);
+    return false;
+  }
+  should_last_packet_instigate_acks_ = false;
   MaybeUpdateAckTimeout();
   return true;
 }
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc
index 9fa3a16..e2d4fcb 100644
--- a/quiche/quic/core/quic_connection_test.cc
+++ b/quiche/quic/core/quic_connection_test.cc
@@ -15924,16 +15924,37 @@
     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();
+      if (frame_type != IMMEDIATE_ACK_FRAME) {
+        ASSERT_TRUE(connection_.HasPendingAcks()) << frame;
+        // Flush ACK.
+        clock_.AdvanceTime(DefaultDelayedAckTime());
+        connection_.GetAckAlarm()->Fire();
+      }
+      EXPECT_FALSE(writer_->ack_frames().empty());
+      writer_->framer()->Reset();  // Clear the visitor.
     }
     EXPECT_FALSE(connection_.HasPendingAcks());
     ASSERT_TRUE(connection_.connected());
   }
 }
 
+TEST_P(QuicConnectionTest, ImmediateAckOverridesOtherFrame) {
+  if (!version().HasIetfQuicFrames()) {
+    return;
+  }
+  QuicFrames frames;
+  connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+  connection_.set_can_receive_ack_frequency_immediate_ack(true);
+  // A PING frame will set the ack timer. IMMEDIATE_ACK should override it to
+  // send an ACK immediately.
+  frames.push_back(QuicFrame(QuicPingFrame()));
+  frames.push_back(QuicFrame(QuicImmediateAckFrame()));
+  writer_->framer()->Reset();  // Clear the visitor.
+  EXPECT_TRUE(writer_->ack_frames().empty());
+  ProcessFramesPacketAtLevel(1, frames, ENCRYPTION_FORWARD_SECURE);
+  EXPECT_FALSE(writer_->ack_frames().empty());
+}
+
 TEST_P(QuicConnectionTest, ReceivedChloAndAck) {
   if (!version().HasIetfQuicFrames()) {
     return;
diff --git a/quiche/quic/core/quic_received_packet_manager.cc b/quiche/quic/core/quic_received_packet_manager.cc
index fa0f64b..56c97cb 100644
--- a/quiche/quic/core/quic_received_packet_manager.cc
+++ b/quiche/quic/core/quic_received_packet_manager.cc
@@ -83,6 +83,7 @@
     ack_frame_.received_packet_times.clear();
   }
   ack_frame_updated_ = true;
+  ack_now_ = false;
 
   // Whether |packet_number| is received out of order.
   bool packet_reordered = false;
@@ -289,6 +290,13 @@
     return;
   }
 
+  if (ack_now_) {
+    // An IMMEDIATE_ACK frame arrived. Send an ack immediately.
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_receive_ack_frequency, 2, 2);
+    ack_timeout_ = now;
+    return;
+  }
+
   if (!ignore_order_ && was_last_packet_missing_ &&
       last_sent_largest_acked_.IsInitialized() &&
       last_received_packet_number < last_sent_largest_acked_) {
diff --git a/quiche/quic/core/quic_received_packet_manager.h b/quiche/quic/core/quic_received_packet_manager.h
index c08bb70..d1feccc 100644
--- a/quiche/quic/core/quic_received_packet_manager.h
+++ b/quiche/quic/core/quic_received_packet_manager.h
@@ -61,6 +61,10 @@
   // received after this call.
   void DontWaitForPacketsBefore(QuicPacketNumber least_unacked);
 
+  // An IMMEDIATE_ACK frame arrived, so update the ack_timeout_ to now the next
+  // time it's set.
+  void OnImmediateAckFrame() { ack_now_ = true; }
+
   // Called to update ack_timeout_ to the time when an ACK needs to be sent. A
   // caller can decide whether and when to send an ACK by retrieving
   // ack_timeout_. If ack_timeout_ is not initialized, no ACK needs to be sent.
@@ -211,6 +215,9 @@
   // The current packet is CE-marked, and the previous packet was not. This
   // condition should trigger an immediate ACK.
   bool changed_to_ce_marked_ = false;
+  // Because of an IMMEDIATE_ACK frame, the next call to MaybeUpdateAckTimeout
+  // should set the ack timeout to now.
+  bool ack_now_ = false;
 
   // Last sent largest acked, which gets updated when ACK was successfully sent.
   QuicPacketNumber last_sent_largest_acked_;
diff --git a/quiche/quic/core/uber_received_packet_manager.cc b/quiche/quic/core/uber_received_packet_manager.cc
index b82ab9c..2f64754 100644
--- a/quiche/quic/core/uber_received_packet_manager.cc
+++ b/quiche/quic/core/uber_received_packet_manager.cc
@@ -72,6 +72,16 @@
       .DontWaitForPacketsBefore(least_unacked);
 }
 
+void UberReceivedPacketManager::OnImmediateAckFrame() {
+  if (!supports_multiple_packet_number_spaces_) {
+    QUIC_BUG(quic_bug_10495_4)
+        << "Received ImmediateAckFrame when multiple packet number spaces "
+           "is not supported";
+    return;
+  }
+  received_packet_managers_[APPLICATION_DATA].OnImmediateAckFrame();
+}
+
 void UberReceivedPacketManager::MaybeUpdateAckTimeout(
     bool should_last_packet_instigate_acks,
     EncryptionLevel decrypted_packet_level,
diff --git a/quiche/quic/core/uber_received_packet_manager.h b/quiche/quic/core/uber_received_packet_manager.h
index 7f4c7cc..4d60ce0 100644
--- a/quiche/quic/core/uber_received_packet_manager.h
+++ b/quiche/quic/core/uber_received_packet_manager.h
@@ -43,6 +43,9 @@
   void DontWaitForPacketsBefore(EncryptionLevel decrypted_packet_level,
                                 QuicPacketNumber least_unacked);
 
+  // Trigger an immediate ACK.
+  void OnImmediateAckFrame();
+
   // Called after header of last received packet has been successfully processed
   // to update ACK timeout.
   void MaybeUpdateAckTimeout(bool should_last_packet_instigate_acks,
diff --git a/quiche/quic/core/uber_received_packet_manager_test.cc b/quiche/quic/core/uber_received_packet_manager_test.cc
index 083d312..4cab508 100644
--- a/quiche/quic/core/uber_received_packet_manager_test.cc
+++ b/quiche/quic/core/uber_received_packet_manager_test.cc
@@ -6,12 +6,12 @@
 
 #include <algorithm>
 #include <memory>
-#include <utility>
 
 #include "quiche/quic/core/congestion_control/rtt_stats.h"
 #include "quiche/quic/core/crypto/crypto_protocol.h"
 #include "quiche/quic/core/quic_connection_stats.h"
-#include "quiche/quic/core/quic_utils.h"
+#include "quiche/quic/core/quic_packet_number.h"
+#include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/platform/api/quic_test.h"
 #include "quiche/quic/test_tools/mock_clock.h"
 
@@ -565,6 +565,22 @@
                   QuicTime::Delta::FromMilliseconds(11) + kDelayedAckTime);
 }
 
+TEST_F(UberReceivedPacketManagerTest, ImmediateAckFrameTriggersAck) {
+  manager_->EnableMultiplePacketNumberSpacesSupport(Perspective::IS_CLIENT);
+  EXPECT_FALSE(HasPendingAck());
+  RecordPacketReceipt(1, clock_.ApproximateNow());
+  manager_->OnImmediateAckFrame();
+  manager_->MaybeUpdateAckTimeout(kInstigateAck, ENCRYPTION_FORWARD_SECURE,
+                                  QuicPacketNumber(1), clock_.ApproximateNow(),
+                                  clock_.ApproximateNow(), &rtt_stats_);
+  CheckAckTimeout(clock_.ApproximateNow());
+  RecordPacketReceipt(2, clock_.ApproximateNow());
+  manager_->MaybeUpdateAckTimeout(kInstigateAck, ENCRYPTION_FORWARD_SECURE,
+                                  QuicPacketNumber(2), clock_.ApproximateNow(),
+                                  clock_.ApproximateNow(), &rtt_stats_);
+  CheckAckTimeout(clock_.ApproximateNow() + kDelayedAckTime);
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic