Frame/parse IMMEDIATE_ACK frame.

Does not cause sending of the frame under any circumstances.

Protected by FLAGS_quic_reloadable_flag_quic_receive_ack_frequency.

PiperOrigin-RevId: 715943023
diff --git a/build/source_list.bzl b/build/source_list.bzl
index ac160d7..2467288 100644
--- a/build/source_list.bzl
+++ b/build/source_list.bzl
@@ -229,6 +229,7 @@
     "quic/core/frames/quic_frame.h",
     "quic/core/frames/quic_goaway_frame.h",
     "quic/core/frames/quic_handshake_done_frame.h",
+    "quic/core/frames/quic_immediate_ack_frame.h",
     "quic/core/frames/quic_inlined_frame.h",
     "quic/core/frames/quic_max_streams_frame.h",
     "quic/core/frames/quic_message_frame.h",
@@ -574,6 +575,7 @@
     "quic/core/frames/quic_frame.cc",
     "quic/core/frames/quic_goaway_frame.cc",
     "quic/core/frames/quic_handshake_done_frame.cc",
+    "quic/core/frames/quic_immediate_ack_frame.cc",
     "quic/core/frames/quic_max_streams_frame.cc",
     "quic/core/frames/quic_message_frame.cc",
     "quic/core/frames/quic_new_connection_id_frame.cc",
diff --git a/build/source_list.gni b/build/source_list.gni
index a8bf15a..68b97d7 100644
--- a/build/source_list.gni
+++ b/build/source_list.gni
@@ -229,6 +229,7 @@
     "src/quiche/quic/core/frames/quic_frame.h",
     "src/quiche/quic/core/frames/quic_goaway_frame.h",
     "src/quiche/quic/core/frames/quic_handshake_done_frame.h",
+    "src/quiche/quic/core/frames/quic_immediate_ack_frame.h",
     "src/quiche/quic/core/frames/quic_inlined_frame.h",
     "src/quiche/quic/core/frames/quic_max_streams_frame.h",
     "src/quiche/quic/core/frames/quic_message_frame.h",
@@ -574,6 +575,7 @@
     "src/quiche/quic/core/frames/quic_frame.cc",
     "src/quiche/quic/core/frames/quic_goaway_frame.cc",
     "src/quiche/quic/core/frames/quic_handshake_done_frame.cc",
+    "src/quiche/quic/core/frames/quic_immediate_ack_frame.cc",
     "src/quiche/quic/core/frames/quic_max_streams_frame.cc",
     "src/quiche/quic/core/frames/quic_message_frame.cc",
     "src/quiche/quic/core/frames/quic_new_connection_id_frame.cc",
diff --git a/build/source_list.json b/build/source_list.json
index ab414ec..8e88967 100644
--- a/build/source_list.json
+++ b/build/source_list.json
@@ -228,6 +228,7 @@
     "quiche/quic/core/frames/quic_frame.h",
     "quiche/quic/core/frames/quic_goaway_frame.h",
     "quiche/quic/core/frames/quic_handshake_done_frame.h",
+    "quiche/quic/core/frames/quic_immediate_ack_frame.h",
     "quiche/quic/core/frames/quic_inlined_frame.h",
     "quiche/quic/core/frames/quic_max_streams_frame.h",
     "quiche/quic/core/frames/quic_message_frame.h",
@@ -573,6 +574,7 @@
     "quiche/quic/core/frames/quic_frame.cc",
     "quiche/quic/core/frames/quic_goaway_frame.cc",
     "quiche/quic/core/frames/quic_handshake_done_frame.cc",
+    "quiche/quic/core/frames/quic_immediate_ack_frame.cc",
     "quiche/quic/core/frames/quic_max_streams_frame.cc",
     "quiche/quic/core/frames/quic_message_frame.cc",
     "quiche/quic/core/frames/quic_new_connection_id_frame.cc",
diff --git a/quiche/common/quiche_feature_flags_list.h b/quiche/common/quiche_feature_flags_list.h
index e617338..37c825a 100755
--- a/quiche/common/quiche_feature_flags_list.h
+++ b/quiche/common/quiche_feature_flags_list.h
@@ -55,6 +55,7 @@
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_parse_cert_compression_algos_from_chlo, true, true, "If true, parse offered cert compression algorithms from received CHLOs.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_parse_transport_parameters_from_chlo, false, true, "If true, parse QUIC transport parameters from received CHLOs.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_priority_respect_incremental, false, false, "If true, respect the incremental parameter of each stream in QuicWriteBlockedList.")
+QUICHE_FLAG(bool, quiche_reloadable_flag_quic_receive_ack_frequency, false, false, "When true, advertises support for ACK_FREQUENCY and IMMEDIATE_ACK from draft-ietf-quic-ack-frequency-10 and processes them correctly.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_require_handshake_confirmation, true, true, "If true, require handshake confirmation for QUIC connections, functionally disabling 0-rtt handshakes.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_send_placeholder_ticket_when_encrypt_ticket_fails, true, true, "If true, when TicketCrypter fails to encrypt a session ticket, quic::TlsServerHandshaker will send a placeholder ticket, instead of an empty one, to the client.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_stop_reading_also_stops_header_decompression, true, true, "If true, QUIC stream will not continue decompressing buffer headers after StopReading() called.")
diff --git a/quiche/quic/core/chlo_extractor.cc b/quiche/quic/core/chlo_extractor.cc
index 94af81d..1a105c5 100644
--- a/quiche/quic/core/chlo_extractor.cc
+++ b/quiche/quic/core/chlo_extractor.cc
@@ -15,6 +15,7 @@
 #include "quiche/quic/core/crypto/quic_decrypter.h"
 #include "quiche/quic/core/crypto/quic_encrypter.h"
 #include "quiche/quic/core/frames/quic_ack_frequency_frame.h"
+#include "quiche/quic/core/frames/quic_immediate_ack_frame.h"
 #include "quiche/quic/core/frames/quic_reset_stream_at_frame.h"
 #include "quiche/quic/core/quic_framer.h"
 #include "quiche/quic/core/quic_types.h"
@@ -81,7 +82,8 @@
   bool OnPaddingFrame(const QuicPaddingFrame& frame) override;
   bool OnMessageFrame(const QuicMessageFrame& frame) override;
   bool OnHandshakeDoneFrame(const QuicHandshakeDoneFrame& frame) override;
-  bool OnAckFrequencyFrame(const QuicAckFrequencyFrame& farme) override;
+  bool OnAckFrequencyFrame(const QuicAckFrequencyFrame& frame) override;
+  bool OnImmediateAckFrame(const QuicImmediateAckFrame& frame) override;
   bool OnResetStreamAtFrame(const QuicResetStreamAtFrame& frame) override;
   void OnPacketComplete() override {}
   bool IsValidStatelessResetToken(
@@ -311,6 +313,11 @@
   return true;
 }
 
+bool ChloFramerVisitor::OnImmediateAckFrame(
+    const QuicImmediateAckFrame& /*frame*/) {
+  return true;
+}
+
 bool ChloFramerVisitor::IsValidStatelessResetToken(
     const StatelessResetToken& /*token*/) const {
   return false;
diff --git a/quiche/quic/core/frames/quic_frame.cc b/quiche/quic/core/frames/quic_frame.cc
index f798703..3353326 100644
--- a/quiche/quic/core/frames/quic_frame.cc
+++ b/quiche/quic/core/frames/quic_frame.cc
@@ -9,6 +9,7 @@
 #include <string>
 #include <utility>
 
+#include "quiche/quic/core/frames/quic_immediate_ack_frame.h"
 #include "quiche/quic/core/frames/quic_new_connection_id_frame.h"
 #include "quiche/quic/core/frames/quic_reset_stream_at_frame.h"
 #include "quiche/quic/core/frames/quic_retire_connection_id_frame.h"
@@ -87,6 +88,9 @@
 QuicFrame::QuicFrame(QuicAckFrequencyFrame* frame)
     : type(ACK_FREQUENCY_FRAME), ack_frequency_frame(frame) {}
 
+QuicFrame::QuicFrame(QuicImmediateAckFrame frame)
+    : immediate_ack_frame(frame) {}
+
 QuicFrame::QuicFrame(QuicResetStreamAtFrame* frame)
     : type(RESET_STREAM_AT_FRAME), reset_stream_at_frame(frame) {}
 
@@ -127,6 +131,7 @@
     case STOP_SENDING_FRAME:
     case PATH_CHALLENGE_FRAME:
     case PATH_RESPONSE_FRAME:
+    case IMMEDIATE_ACK_FRAME:
       break;
     case ACK_FRAME:
       delete frame->ack_frame;
@@ -436,6 +441,9 @@
     case ACK_FREQUENCY_FRAME:
       copy = QuicFrame(new QuicAckFrequencyFrame(*frame.ack_frequency_frame));
       break;
+    case IMMEDIATE_ACK_FRAME:
+      copy = QuicFrame(QuicImmediateAckFrame(frame.immediate_ack_frame));
+      break;
     case RESET_STREAM_AT_FRAME:
       copy =
           QuicFrame(new QuicResetStreamAtFrame(*frame.reset_stream_at_frame));
@@ -542,6 +550,9 @@
     case ACK_FREQUENCY_FRAME:
       os << "type { ACK_FREQUENCY_FRAME } " << *(frame.ack_frequency_frame);
       break;
+    case IMMEDIATE_ACK_FRAME:
+      os << "type { IMMEDIATE_ACK_FRAME } " << frame.immediate_ack_frame;
+      break;
     case RESET_STREAM_AT_FRAME:
       os << "type { RESET_STREAM_AT_FRAME } " << *(frame.reset_stream_at_frame);
       break;
diff --git a/quiche/quic/core/frames/quic_frame.h b/quiche/quic/core/frames/quic_frame.h
index ffb9f77..3485e4e 100644
--- a/quiche/quic/core/frames/quic_frame.h
+++ b/quiche/quic/core/frames/quic_frame.h
@@ -18,6 +18,7 @@
 #include "quiche/quic/core/frames/quic_crypto_frame.h"
 #include "quiche/quic/core/frames/quic_goaway_frame.h"
 #include "quiche/quic/core/frames/quic_handshake_done_frame.h"
+#include "quiche/quic/core/frames/quic_immediate_ack_frame.h"
 #include "quiche/quic/core/frames/quic_max_streams_frame.h"
 #include "quiche/quic/core/frames/quic_message_frame.h"
 #include "quiche/quic/core/frames/quic_mtu_discovery_frame.h"
@@ -65,6 +66,7 @@
   explicit QuicFrame(QuicStopSendingFrame frame);
   explicit QuicFrame(QuicPathChallengeFrame frame);
   explicit QuicFrame(QuicPathResponseFrame frame);
+  explicit QuicFrame(QuicImmediateAckFrame immediate_ack_frame);
 
   explicit QuicFrame(QuicAckFrame* frame);
   explicit QuicFrame(QuicRstStreamFrame* frame);
@@ -99,6 +101,7 @@
     QuicStopSendingFrame stop_sending_frame;
     QuicPathChallengeFrame path_challenge_frame;
     QuicPathResponseFrame path_response_frame;
+    QuicImmediateAckFrame immediate_ack_frame;
 
     // Out of line frames.
     struct {
diff --git a/quiche/quic/core/frames/quic_frames_test.cc b/quiche/quic/core/frames/quic_frames_test.cc
index 7179d18..e6a843d 100644
--- a/quiche/quic/core/frames/quic_frames_test.cc
+++ b/quiche/quic/core/frames/quic_frames_test.cc
@@ -10,6 +10,7 @@
 #include "quiche/quic/core/frames/quic_connection_close_frame.h"
 #include "quiche/quic/core/frames/quic_frame.h"
 #include "quiche/quic/core/frames/quic_goaway_frame.h"
+#include "quiche/quic/core/frames/quic_immediate_ack_frame.h"
 #include "quiche/quic/core/frames/quic_mtu_discovery_frame.h"
 #include "quiche/quic/core/frames/quic_new_connection_id_frame.h"
 #include "quiche/quic/core/frames/quic_padding_frame.h"
@@ -345,6 +346,15 @@
   EXPECT_TRUE(IsControlFrame(frame.type));
 }
 
+TEST_F(QuicFramesTest, QuicImmediateAckFrameToString) {
+  QuicImmediateAckFrame immediate_ack_frame;
+  QuicFrame frame(immediate_ack_frame);
+  std::ostringstream stream;
+  stream << frame.immediate_ack_frame;
+  EXPECT_EQ("{ }\n", stream.str());
+  EXPECT_FALSE(IsControlFrame(frame.type));
+}
+
 TEST_F(QuicFramesTest, StreamFrameToString) {
   QuicStreamFrame frame;
   frame.stream_id = 1;
@@ -661,6 +671,9 @@
       case ACK_FREQUENCY_FRAME:
         frames.push_back(QuicFrame(new QuicAckFrequencyFrame()));
         break;
+      case IMMEDIATE_ACK_FRAME:
+        frames.push_back(QuicFrame(QuicImmediateAckFrame()));
+        break;
       case RESET_STREAM_AT_FRAME:
         frames.push_back(QuicFrame(new QuicResetStreamAtFrame()));
         break;
diff --git a/quiche/quic/core/frames/quic_immediate_ack_frame.cc b/quiche/quic/core/frames/quic_immediate_ack_frame.cc
new file mode 100644
index 0000000..bd107f8
--- /dev/null
+++ b/quiche/quic/core/frames/quic_immediate_ack_frame.cc
@@ -0,0 +1,23 @@
+// Copyright (c) 2025 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/quic/core/frames/quic_immediate_ack_frame.h"
+
+#include <ostream>
+
+#include "quiche/quic/core/frames/quic_inlined_frame.h"
+#include "quiche/quic/core/quic_types.h"
+
+namespace quic {
+
+QuicImmediateAckFrame::QuicImmediateAckFrame()
+    : QuicInlinedFrame(IMMEDIATE_ACK_FRAME) {}
+
+std::ostream& operator<<(std::ostream& os,
+                         const QuicImmediateAckFrame& /*immediate_ack_frame*/) {
+  os << "{ }\n";
+  return os;
+}
+
+}  // namespace quic
diff --git a/quiche/quic/core/frames/quic_immediate_ack_frame.h b/quiche/quic/core/frames/quic_immediate_ack_frame.h
new file mode 100644
index 0000000..0ae2604
--- /dev/null
+++ b/quiche/quic/core/frames/quic_immediate_ack_frame.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2025 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_FRAMES_QUIC_IMMEDIATE_ACK_FRAME_H_
+#define QUICHE_QUIC_CORE_FRAMES_QUIC_IMMEDIATE_ACK_FRAME_H_
+
+#include <ostream>
+
+#include "quiche/quic/core/frames/quic_inlined_frame.h"
+#include "quiche/quic/core/quic_types.h"
+#include "quiche/common/platform/api/quiche_export.h"
+
+namespace quic {
+
+// A frame that allows the sender to request an immediate ack from the receiver.
+// Not a retransmittable frame.
+struct QUICHE_EXPORT QuicImmediateAckFrame
+    : QuicInlinedFrame<QuicImmediateAckFrame> {
+  QuicImmediateAckFrame();
+
+  friend QUICHE_EXPORT std::ostream& operator<<(
+      std::ostream& os, const QuicImmediateAckFrame& immediate_ack_frame);
+
+  QuicFrameType type;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_FRAMES_QUIC_IMMEDIATE_ACK_FRAME_H_
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc
index 92a9e51..6a12e48 100644
--- a/quiche/quic/core/quic_connection.cc
+++ b/quiche/quic/core/quic_connection.cc
@@ -33,6 +33,8 @@
 #include "quiche/quic/core/crypto/crypto_utils.h"
 #include "quiche/quic/core/crypto/quic_decrypter.h"
 #include "quiche/quic/core/crypto/quic_encrypter.h"
+#include "quiche/quic/core/frames/quic_ack_frequency_frame.h"
+#include "quiche/quic/core/frames/quic_immediate_ack_frame.h"
 #include "quiche/quic/core/frames/quic_reset_stream_at_frame.h"
 #include "quiche/quic/core/quic_bandwidth.h"
 #include "quiche/quic/core/quic_config.h"
@@ -265,6 +267,9 @@
     AddKnownServerAddress(initial_peer_address);
   }
   packet_creator_.SetDefaultPeerAddress(initial_peer_address);
+  can_receive_ack_frequency_immediate_ack_ =
+      version().HasIetfQuicFrames() &&
+      GetQuicReloadableFlag(quic_receive_ack_frequency);
 }
 
 void QuicConnection::InstallInitialCrypters(QuicConnectionId connection_id) {
@@ -2101,6 +2106,26 @@
   return true;
 }
 
+bool QuicConnection::OnImmediateAckFrame(const QuicImmediateAckFrame& frame) {
+  QUIC_BUG_IF(quic_bug_immediate_ack_frame_connection_closed, !connected_)
+      << "Processing IMMEDIATE_ACK frame when connection "
+         "is closed. Received packet info: "
+      << last_received_packet_info_;
+  if (debug_visitor_ != nullptr) {
+    debug_visitor_->OnImmediateAckFrame(frame);
+  }
+  if (!UpdatePacketContent(IMMEDIATE_ACK_FRAME)) {
+    return false;
+  }
+  if (!can_receive_ack_frequency_immediate_ack_) {
+    QUIC_LOG_EVERY_N_SEC(ERROR, 120) << "Got unexpected ImmediateAck Frame.";
+    return false;
+  }
+  QUIC_RELOADABLE_FLAG_COUNT_N(quic_receive_ack_frequency, 1, 1);
+  MaybeUpdateAckTimeout();
+  return true;
+}
+
 bool QuicConnection::OnResetStreamAtFrame(const QuicResetStreamAtFrame& frame) {
   QUIC_BUG_IF(OnResetStreamAtFrame_connection_closed, !connected_)
       << "Processing RESET_STREAM_AT frame while the connection is closed. "
diff --git a/quiche/quic/core/quic_connection.h b/quiche/quic/core/quic_connection.h
index 305d1a9..b295e59 100644
--- a/quiche/quic/core/quic_connection.h
+++ b/quiche/quic/core/quic_connection.h
@@ -32,6 +32,7 @@
 #include "quiche/quic/core/crypto/quic_random.h"
 #include "quiche/quic/core/crypto/transport_parameters.h"
 #include "quiche/quic/core/frames/quic_ack_frequency_frame.h"
+#include "quiche/quic/core/frames/quic_immediate_ack_frame.h"
 #include "quiche/quic/core/frames/quic_max_streams_frame.h"
 #include "quiche/quic/core/frames/quic_new_connection_id_frame.h"
 #include "quiche/quic/core/frames/quic_reset_stream_at_frame.h"
@@ -433,6 +434,9 @@
   // Called when an AckFrequencyFrame has been parsed.
   virtual void OnAckFrequencyFrame(const QuicAckFrequencyFrame& /*frame*/) {}
 
+  // Called when an ImmediateAckFrame has been parsed.
+  virtual void OnImmediateAckFrame(const QuicImmediateAckFrame& /*frame*/) {}
+
   // Called when a ResetStreamAtFrame has been parsed.
   virtual void OnResetStreamAtFrame(const QuicResetStreamAtFrame& /*frame*/) {}
 
@@ -755,6 +759,7 @@
   bool OnMessageFrame(const QuicMessageFrame& frame) override;
   bool OnHandshakeDoneFrame(const QuicHandshakeDoneFrame& frame) override;
   bool OnAckFrequencyFrame(const QuicAckFrequencyFrame& frame) override;
+  bool OnImmediateAckFrame(const QuicImmediateAckFrame& frame) override;
   bool OnResetStreamAtFrame(const QuicResetStreamAtFrame& frame) override;
   void OnPacketComplete() override;
   bool IsValidStatelessResetToken(
@@ -1318,6 +1323,10 @@
     can_receive_ack_frequency_frame_ = true;
   }
 
+  void set_can_receive_ack_frequency_immediate_ack(bool can_receive) {
+    can_receive_ack_frequency_immediate_ack_ = can_receive;
+  }
+
   bool is_processing_packet() const { return framer_.is_processing_packet(); }
 
   bool HasPendingPathValidation() const;
@@ -2481,6 +2490,7 @@
 
   // True if AckFrequencyFrame is supported.
   bool can_receive_ack_frequency_frame_ = false;
+  bool can_receive_ack_frequency_immediate_ack_ = false;
 
   // Indicate whether coalescing is done.
   bool coalescing_done_ = false;
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc
index c9bc761..9fa3a16 100644
--- a/quiche/quic/core/quic_connection_test.cc
+++ b/quiche/quic/core/quic_connection_test.cc
@@ -15798,6 +15798,7 @@
   QuicConnectionPeer::GetSelfIssuedConnectionIdManager(&connection_)
       ->MaybeSendNewConnectionIds();
   connection_.set_can_receive_ack_frequency_frame();
+  connection_.set_can_receive_ack_frequency_immediate_ack(true);
 
   QuicAckFrame ack_frame = InitAckFrame(1);
   QuicRstStreamFrame rst_stream_frame;
@@ -15903,6 +15904,9 @@
       case ACK_FREQUENCY_FRAME:
         frame = QuicFrame(&ack_frequency_frame);
         break;
+      case IMMEDIATE_ACK_FRAME:
+        frame = QuicFrame(QuicImmediateAckFrame());
+        break;
       case RESET_STREAM_AT_FRAME:
         frame = QuicFrame(&reset_stream_at_frame);
         break;
diff --git a/quiche/quic/core/quic_framer.cc b/quiche/quic/core/quic_framer.cc
index 1cddd90..a727cad 100644
--- a/quiche/quic/core/quic_framer.cc
+++ b/quiche/quic/core/quic_framer.cc
@@ -32,11 +32,11 @@
 #include "quiche/quic/core/crypto/crypto_protocol.h"
 #include "quiche/quic/core/crypto/crypto_utils.h"
 #include "quiche/quic/core/crypto/null_decrypter.h"
-#include "quiche/quic/core/crypto/null_encrypter.h"
 #include "quiche/quic/core/crypto/quic_decrypter.h"
 #include "quiche/quic/core/crypto/quic_encrypter.h"
 #include "quiche/quic/core/crypto/quic_random.h"
 #include "quiche/quic/core/frames/quic_ack_frequency_frame.h"
+#include "quiche/quic/core/frames/quic_immediate_ack_frame.h"
 #include "quiche/quic/core/frames/quic_reset_stream_at_frame.h"
 #include "quiche/quic/core/quic_connection_id.h"
 #include "quiche/quic/core/quic_constants.h"
@@ -56,7 +56,6 @@
 #include "quiche/quic/platform/api/quic_flags.h"
 #include "quiche/quic/platform/api/quic_ip_address_family.h"
 #include "quiche/quic/platform/api/quic_logging.h"
-#include "quiche/quic/platform/api/quic_stack_trace.h"
 #include "quiche/common/quiche_text_utils.h"
 #include "quiche/common/wire_serialization.h"
 
@@ -693,6 +692,9 @@
       return kQuicFrameTypeSize;
     case ACK_FREQUENCY_FRAME:
       return GetAckFrequencyFrameSize(*frame.ack_frequency_frame);
+    case IMMEDIATE_ACK_FRAME:
+      // IMMEDIATE_ACK has no payload.
+      return QuicDataWriter::GetVarInt62Len(IETF_IMMEDIATE_ACK);
     case RESET_STREAM_AT_FRAME:
       return GetResetStreamAtFrameSize(*frame.reset_stream_at_frame);
     case STREAM_FRAME:
@@ -1209,6 +1211,9 @@
           return 0;
         }
         break;
+      case IMMEDIATE_ACK_FRAME:
+        // IMMEDIATE_ACK has no payload.
+        break;
       case RESET_STREAM_AT_FRAME:
         QUIC_BUG_IF(reset_stream_at_appended_while_disabled,
                     !process_reset_stream_at_)
@@ -3143,6 +3148,18 @@
           }
           break;
         }
+        case IETF_IMMEDIATE_ACK: {
+          // IMMEDIATE_ACK has no payload.
+          QuicImmediateAckFrame frame;
+          QUIC_DVLOG(2) << ENDPOINT << "Processing IETF immediate ack frame "
+                        << frame;
+          if (!visitor_->OnImmediateAckFrame(frame)) {
+            QUIC_DVLOG(1) << "Visitor asked to stop further processing.";
+            // Returning true since there was no parsing error.
+            return true;
+          }
+          break;
+        }
         case IETF_RESET_STREAM_AT: {
           if (!process_reset_stream_at_) {
             set_detailed_error("RESET_STREAM_AT not enabled.");
@@ -4992,6 +5009,9 @@
     case ACK_FREQUENCY_FRAME:
       type_byte = IETF_ACK_FREQUENCY;
       break;
+    case IMMEDIATE_ACK_FRAME:
+      type_byte = IETF_IMMEDIATE_ACK;
+      break;
     case RESET_STREAM_AT_FRAME:
       type_byte = IETF_RESET_STREAM_AT;
       break;
diff --git a/quiche/quic/core/quic_framer.h b/quiche/quic/core/quic_framer.h
index db92e0a..a941e02 100644
--- a/quiche/quic/core/quic_framer.h
+++ b/quiche/quic/core/quic_framer.h
@@ -15,11 +15,11 @@
 #include "quiche/quic/core/crypto/quic_decrypter.h"
 #include "quiche/quic/core/crypto/quic_encrypter.h"
 #include "quiche/quic/core/crypto/quic_random.h"
+#include "quiche/quic/core/frames/quic_immediate_ack_frame.h"
 #include "quiche/quic/core/frames/quic_reset_stream_at_frame.h"
 #include "quiche/quic/core/quic_connection_id.h"
 #include "quiche/quic/core/quic_packets.h"
 #include "quiche/quic/core/quic_types.h"
-#include "quiche/quic/platform/api/quic_export.h"
 
 namespace quic {
 
@@ -223,6 +223,9 @@
   // Called when an AckFrequencyFrame has been parsed.
   virtual bool OnAckFrequencyFrame(const QuicAckFrequencyFrame& frame) = 0;
 
+  // Called when an ImmediateAckFrame has been parsed.
+  virtual bool OnImmediateAckFrame(const QuicImmediateAckFrame& frame) = 0;
+
   // Called when an ResetStreamAtFrame has been parsed.
   virtual bool OnResetStreamAtFrame(const QuicResetStreamAtFrame& frame) = 0;
 
diff --git a/quiche/quic/core/quic_framer_test.cc b/quiche/quic/core/quic_framer_test.cc
index a11b638..44fd4f9 100644
--- a/quiche/quic/core/quic_framer_test.cc
+++ b/quiche/quic/core/quic_framer_test.cc
@@ -464,6 +464,15 @@
     return true;
   }
 
+  bool OnImmediateAckFrame(const QuicImmediateAckFrame& frame) override {
+    ++frame_count_;
+    immediate_ack_frames_.emplace_back(
+        std::make_unique<QuicImmediateAckFrame>(frame));
+    QUICHE_DCHECK(VersionHasIetfQuicFrames(transport_version_));
+    EXPECT_EQ(IETF_IMMEDIATE_ACK, framer_->current_received_frame_type());
+    return true;
+  }
+
   bool OnResetStreamAtFrame(const QuicResetStreamAtFrame& frame) override {
     ++frame_count_;
     reset_stream_at_frames_.push_back(
@@ -664,6 +673,7 @@
   std::vector<std::unique_ptr<QuicMessageFrame>> message_frames_;
   std::vector<std::unique_ptr<QuicHandshakeDoneFrame>> handshake_done_frames_;
   std::vector<std::unique_ptr<QuicAckFrequencyFrame>> ack_frequency_frames_;
+  std::vector<std::unique_ptr<QuicImmediateAckFrame>> immediate_ack_frames_;
   std::vector<std::unique_ptr<QuicResetStreamAtFrame>> reset_stream_at_frames_;
   std::vector<std::unique_ptr<QuicEncryptedPacket>> coalesced_packets_;
   std::vector<std::unique_ptr<QuicEncryptedPacket>> undecryptable_packets_;
@@ -4807,6 +4817,38 @@
   EXPECT_EQ(true, frame->ignore_order);
 }
 
+TEST_P(QuicFramerTest, ParseImmediateAckFrame) {
+  SetDecrypterLevel(ENCRYPTION_FORWARD_SECURE);
+  // clang-format off
+  unsigned char packet[] = {
+     // type (short header, 4 byte packet number)
+     0x43,
+     // connection_id
+     0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+     // packet number
+     0x12, 0x34, 0x56, 0x78,
+
+     // immediate ack frame type
+     0x1F,
+  };
+  // clang-format on
+
+  if (!VersionHasIetfQuicFrames(framer_.transport_version())) {
+    return;
+  }
+
+  QuicEncryptedPacket encrypted(AsChars(packet), ABSL_ARRAYSIZE(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_THAT(framer_.error(), IsQuicNoError());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      kPacket8ByteConnectionId, kPacket0ByteConnectionId));
+
+  ASSERT_EQ(1u, visitor_.immediate_ack_frames_.size());
+}
+
 TEST_P(QuicFramerTest, ParseResetStreamAtFrame) {
   SetDecrypterLevel(ENCRYPTION_FORWARD_SECURE);
   // clang-format off
@@ -8189,6 +8231,41 @@
       ABSL_ARRAYSIZE(packet));
 }
 
+TEST_P(QuicFramerTest, BuildImmediateAckPacket) {
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicImmediateAckFrame immediate_ack_frame;
+  QuicFrames frames = {QuicFrame(immediate_ack_frame)};
+
+  // clang-format off
+  unsigned char packet[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (Immediate Ack frame)
+    0x1f,
+  };
+  // clang-format on
+  if (!VersionHasIetfQuicFrames(framer_.transport_version())) {
+    return;
+  }
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  quiche::test::CompareCharArraysWithHexError(
+      "constructed packet", data->data(), data->length(), AsChars(packet),
+      ABSL_ARRAYSIZE(packet));
+}
+
 TEST_P(QuicFramerTest, BuildResetStreamAtPacket) {
   if (!VersionHasIetfQuicFrames(framer_.transport_version())) {
     return;
diff --git a/quiche/quic/core/quic_packet_creator.cc b/quiche/quic/core/quic_packet_creator.cc
index 5ce9033..29ede80 100644
--- a/quiche/quic/core/quic_packet_creator.cc
+++ b/quiche/quic/core/quic_packet_creator.cc
@@ -1976,6 +1976,8 @@
        frame.type != ACK_FREQUENCY_FRAME))
       << ENDPOINT << frame.type << " not allowed at "
       << packet_.encryption_level;
+  QUICHE_DCHECK(packet_.encryption_level == ENCRYPTION_FORWARD_SECURE ||
+                frame.type != IMMEDIATE_ACK_FRAME);
 
   if (frame.type == STREAM_FRAME) {
     if (MaybeCoalesceStreamFrame(frame.stream_frame)) {
diff --git a/quiche/quic/core/quic_trace_visitor.cc b/quiche/quic/core/quic_trace_visitor.cc
index 2b049ca..4644e0d 100644
--- a/quiche/quic/core/quic_trace_visitor.cc
+++ b/quiche/quic/core/quic_trace_visitor.cc
@@ -78,6 +78,7 @@
       case MTU_DISCOVERY_FRAME:
       case STOP_WAITING_FRAME:
       case ACK_FRAME:
+      case IMMEDIATE_ACK_FRAME:
         QUIC_BUG(quic_bug_12732_1)
             << "Frames of type are not retransmittable and are not supposed "
                "to be in retransmittable_frames";
@@ -227,6 +228,7 @@
     case CRYPTO_FRAME:
     case NEW_TOKEN_FRAME:
     case ACK_FREQUENCY_FRAME:
+    case IMMEDIATE_ACK_FRAME:
     case RESET_STREAM_AT_FRAME:
       break;
 
diff --git a/quiche/quic/core/quic_types.cc b/quiche/quic/core/quic_types.cc
index fff80a9..aa69d02 100644
--- a/quiche/quic/core/quic_types.cc
+++ b/quiche/quic/core/quic_types.cc
@@ -160,6 +160,7 @@
     RETURN_STRING_LITERAL(NEW_TOKEN_FRAME)
     RETURN_STRING_LITERAL(RETIRE_CONNECTION_ID_FRAME)
     RETURN_STRING_LITERAL(ACK_FREQUENCY_FRAME)
+    RETURN_STRING_LITERAL(IMMEDIATE_ACK_FRAME)
     RETURN_STRING_LITERAL(RESET_STREAM_AT_FRAME)
     RETURN_STRING_LITERAL(NUM_FRAME_TYPES)
   }
diff --git a/quiche/quic/core/quic_types.h b/quiche/quic/core/quic_types.h
index 2ffd824..f0649bd 100644
--- a/quiche/quic/core/quic_types.h
+++ b/quiche/quic/core/quic_types.h
@@ -275,6 +275,7 @@
   NEW_TOKEN_FRAME,
   RETIRE_CONNECTION_ID_FRAME,
   ACK_FREQUENCY_FRAME,
+  IMMEDIATE_ACK_FRAME,
   RESET_STREAM_AT_FRAME,
 
   NUM_FRAME_TYPES
@@ -332,6 +333,8 @@
   IETF_APPLICATION_CLOSE = 0x1d,
 
   IETF_HANDSHAKE_DONE = 0x1e,
+  // See draft-ietf-quic-ack-frequency.
+  IETF_IMMEDIATE_ACK = 0x1f,
 
   // The MESSAGE frame type has not yet been fully standardized.
   // QUIC versions starting with 46 and before 99 use 0x20-0x21.
diff --git a/quiche/quic/core/quic_unacked_packet_map.cc b/quiche/quic/core/quic_unacked_packet_map.cc
index d3e3ed3..6192e00 100644
--- a/quiche/quic/core/quic_unacked_packet_map.cc
+++ b/quiche/quic/core/quic_unacked_packet_map.cc
@@ -57,6 +57,7 @@
   kRetireConnectionIdFrameBitfield = 1 << 21,
   kAckFrequencyFrameBitfield = 1 << 22,
   kResetStreamAtFrameBitfield = 1 << 23,
+  kImmediateAckFrameBitfield = 1 << 24,
 };
 
 QuicFrameTypeBitfield GetFrameTypeBitfield(QuicFrameType type) {
@@ -107,6 +108,8 @@
       return kRetireConnectionIdFrameBitfield;
     case ACK_FREQUENCY_FRAME:
       return kAckFrequencyFrameBitfield;
+    case IMMEDIATE_ACK_FRAME:
+      return kImmediateAckFrameBitfield;
     case RESET_STREAM_AT_FRAME:
       return kResetStreamAtFrameBitfield;
     case NUM_FRAME_TYPES:
diff --git a/quiche/quic/core/quic_utils.cc b/quiche/quic/core/quic_utils.cc
index e2663f3..057c087 100644
--- a/quiche/quic/core/quic_utils.cc
+++ b/quiche/quic/core/quic_utils.cc
@@ -248,6 +248,7 @@
     case MTU_DISCOVERY_FRAME:
     case PATH_CHALLENGE_FRAME:
     case PATH_RESPONSE_FRAME:
+    case IMMEDIATE_ACK_FRAME:
       return false;
     default:
       return true;
diff --git a/quiche/quic/core/tls_chlo_extractor.h b/quiche/quic/core/tls_chlo_extractor.h
index 297063c..f24da45 100644
--- a/quiche/quic/core/tls_chlo_extractor.h
+++ b/quiche/quic/core/tls_chlo_extractor.h
@@ -13,12 +13,12 @@
 #include "absl/types/span.h"
 #include "openssl/ssl.h"
 #include "quiche/quic/core/frames/quic_ack_frequency_frame.h"
+#include "quiche/quic/core/frames/quic_immediate_ack_frame.h"
 #include "quiche/quic/core/frames/quic_reset_stream_at_frame.h"
 #include "quiche/quic/core/quic_framer.h"
 #include "quiche/quic/core/quic_packets.h"
 #include "quiche/quic/core/quic_stream_sequencer.h"
 #include "quiche/quic/core/quic_types.h"
-#include "quiche/quic/platform/api/quic_export.h"
 
 namespace quic {
 
@@ -184,6 +184,9 @@
   bool OnAckFrequencyFrame(const QuicAckFrequencyFrame& /*frame*/) override {
     return true;
   }
+  bool OnImmediateAckFrame(const QuicImmediateAckFrame& /*frame*/) override {
+    return true;
+  }
   bool OnResetStreamAtFrame(const QuicResetStreamAtFrame& /*frame*/) override {
     return true;
   }
diff --git a/quiche/quic/test_tools/quic_test_utils.cc b/quiche/quic/test_tools/quic_test_utils.cc
index a2d0798..2b49ce2 100644
--- a/quiche/quic/test_tools/quic_test_utils.cc
+++ b/quiche/quic/test_tools/quic_test_utils.cc
@@ -20,11 +20,7 @@
 #include "openssl/sha.h"
 #include "quiche/quic/core/crypto/crypto_framer.h"
 #include "quiche/quic/core/crypto/crypto_handshake.h"
-#include "quiche/quic/core/crypto/crypto_utils.h"
-#include "quiche/quic/core/crypto/null_decrypter.h"
-#include "quiche/quic/core/crypto/null_encrypter.h"
-#include "quiche/quic/core/crypto/quic_decrypter.h"
-#include "quiche/quic/core/crypto/quic_encrypter.h"
+#include "quiche/quic/core/frames/quic_immediate_ack_frame.h"
 #include "quiche/quic/core/http/quic_spdy_client_session.h"
 #include "quiche/quic/core/quic_config.h"
 #include "quiche/quic/core/quic_data_writer.h"
@@ -35,8 +31,6 @@
 #include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/core/quic_utils.h"
 #include "quiche/quic/core/quic_versions.h"
-#include "quiche/quic/platform/api/quic_flags.h"
-#include "quiche/quic/platform/api/quic_logging.h"
 #include "quiche/quic/test_tools/crypto_test_utils.h"
 #include "quiche/quic/test_tools/quic_config_peer.h"
 #include "quiche/quic/test_tools/quic_connection_peer.h"
@@ -449,6 +443,11 @@
   return true;
 }
 
+bool NoOpFramerVisitor::OnImmediateAckFrame(
+    const QuicImmediateAckFrame& /*frame*/) {
+  return true;
+}
+
 bool NoOpFramerVisitor::OnResetStreamAtFrame(
     const QuicResetStreamAtFrame& /*frame*/) {
   return true;
@@ -1076,14 +1075,14 @@
       BuildUnsizedDataPacket(&framer, header, frames));
   EXPECT_TRUE(packet != nullptr);
 
-  // Now set the frame type to 0x1F, which is an invalid frame type.
+  // Now set the frame type to 0x3F, which is an invalid frame type.
   reinterpret_cast<unsigned char*>(
       packet->mutable_data())[GetStartOfEncryptedData(
       framer.transport_version(),
       GetIncludedDestinationConnectionIdLength(header),
       GetIncludedSourceConnectionIdLength(header), version_flag,
       false /* no diversification nonce */, packet_number_length,
-      header.retry_token_length_length, 0, header.length_length)] = 0x1F;
+      header.retry_token_length_length, 0, header.length_length)] = 0x3F;
 
   char* buffer = new char[kMaxOutgoingPacketSize];
   size_t encrypted_length =
diff --git a/quiche/quic/test_tools/quic_test_utils.h b/quiche/quic/test_tools/quic_test_utils.h
index 10f9fb1..8946ccf 100644
--- a/quiche/quic/test_tools/quic_test_utils.h
+++ b/quiche/quic/test_tools/quic_test_utils.h
@@ -21,6 +21,7 @@
 #include "quiche/quic/core/congestion_control/loss_detection_interface.h"
 #include "quiche/quic/core/congestion_control/send_algorithm_interface.h"
 #include "quiche/quic/core/crypto/transport_parameters.h"
+#include "quiche/quic/core/frames/quic_immediate_ack_frame.h"
 #include "quiche/quic/core/frames/quic_reset_stream_at_frame.h"
 #include "quiche/quic/core/http/http_decoder.h"
 #include "quiche/quic/core/http/quic_server_session_base.h"
@@ -357,6 +358,8 @@
               (override));
   MOCK_METHOD(bool, OnAckFrequencyFrame, (const QuicAckFrequencyFrame& frame),
               (override));
+  MOCK_METHOD(bool, OnImmediateAckFrame, (const QuicImmediateAckFrame& frame),
+              (override));
   MOCK_METHOD(bool, OnResetStreamAtFrame, (const QuicResetStreamAtFrame& frame),
               (override));
   MOCK_METHOD(void, OnPacketComplete, (), (override));
@@ -426,6 +429,7 @@
   bool OnMessageFrame(const QuicMessageFrame& frame) override;
   bool OnHandshakeDoneFrame(const QuicHandshakeDoneFrame& frame) override;
   bool OnAckFrequencyFrame(const QuicAckFrequencyFrame& frame) override;
+  bool OnImmediateAckFrame(const QuicImmediateAckFrame& frame) override;
   bool OnResetStreamAtFrame(const QuicResetStreamAtFrame& frame) override;
   void OnPacketComplete() override {}
   bool IsValidStatelessResetToken(
diff --git a/quiche/quic/test_tools/simple_quic_framer.cc b/quiche/quic/test_tools/simple_quic_framer.cc
index 8ad6656..90b75e8 100644
--- a/quiche/quic/test_tools/simple_quic_framer.cc
+++ b/quiche/quic/test_tools/simple_quic_framer.cc
@@ -7,13 +7,13 @@
 #include <memory>
 #include <optional>
 #include <string>
-#include <utility>
 #include <vector>
 
 #include "absl/memory/memory.h"
 #include "absl/strings/string_view.h"
 #include "quiche/quic/core/crypto/quic_decrypter.h"
 #include "quiche/quic/core/crypto/quic_encrypter.h"
+#include "quiche/quic/core/frames/quic_immediate_ack_frame.h"
 #include "quiche/quic/core/frames/quic_reset_stream_at_frame.h"
 #include "quiche/quic/core/quic_types.h"
 
@@ -240,6 +240,12 @@
     return true;
   }
 
+  bool OnImmediateAckFrame(const QuicImmediateAckFrame& frame) override {
+    immediate_ack_frames_.push_back(frame);
+    frame_types_.push_back(IMMEDIATE_ACK_FRAME);
+    return true;
+  }
+
   bool OnResetStreamAtFrame(const QuicResetStreamAtFrame& frame) override {
     reset_stream_at_frames_.push_back(frame);
     frame_types_.push_back(RESET_STREAM_AT_FRAME);
@@ -349,6 +355,7 @@
   std::vector<QuicMessageFrame> message_frames_;
   std::vector<QuicHandshakeDoneFrame> handshake_done_frames_;
   std::vector<QuicAckFrequencyFrame> ack_frequency_frames_;
+  std::vector<QuicImmediateAckFrame> immediate_ack_frames_;
   std::vector<QuicResetStreamAtFrame> reset_stream_at_frames_;
   std::vector<std::unique_ptr<std::string>> stream_data_;
   std::vector<std::unique_ptr<std::string>> crypto_data_;
diff --git a/quiche/quic/tools/quic_packet_printer_bin.cc b/quiche/quic/tools/quic_packet_printer_bin.cc
index 95d0d1d..9aad0aa 100644
--- a/quiche/quic/tools/quic_packet_printer_bin.cc
+++ b/quiche/quic/tools/quic_packet_printer_bin.cc
@@ -34,12 +34,10 @@
 
 #include "absl/strings/escaping.h"
 #include "absl/strings/string_view.h"
+#include "quiche/quic/core/frames/quic_immediate_ack_frame.h"
 #include "quiche/quic/core/quic_framer.h"
 #include "quiche/quic/core/quic_types.h"
-#include "quiche/quic/core/quic_utils.h"
-#include "quiche/quic/platform/api/quic_flags.h"
 #include "quiche/common/platform/api/quiche_command_line_flags.h"
-#include "quiche/common/quiche_text_utils.h"
 
 DEFINE_QUICHE_COMMAND_LINE_FLAG(std::string, quic_version, "",
                                 "If set, specify the QUIC version to use.");
@@ -217,6 +215,10 @@
     std::cerr << "OnAckFrequencyFrame: " << frame;
     return true;
   }
+  bool OnImmediateAckFrame(const QuicImmediateAckFrame& frame) override {
+    std::cerr << "OnImmediateAckFrame: " << frame;
+    return true;
+  }
   bool OnResetStreamAtFrame(const QuicResetStreamAtFrame& frame) override {
     std::cerr << "OnResetStreamAtFrame: " << frame;
     return true;