Patch in 248417588: renjietang Send HEADERS on the request stream
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc
index c72622c..59159a5 100644
--- a/quic/core/http/end_to_end_test.cc
+++ b/quic/core/http/end_to_end_test.cc
@@ -11,6 +11,21 @@
 #include <utility>
 #include <vector>
 
+<<<<<<<
+|||||||
+#include "net/third_party/quic/core/crypto/null_encrypter.h"
+#include "net/third_party/quic/core/http/quic_spdy_client_stream.h"
+#include "net/third_party/quic/core/quic_epoll_connection_helper.h"
+#include "net/third_party/quic/core/quic_error_codes.h"
+#include "net/third_party/quic/core/quic_framer.h"
+=======
+#include "net/third_party/quic/core/crypto/null_encrypter.h"
+#include "net/third_party/quic/core/http/quic_spdy_client_stream.h"
+#include "net/third_party/quic/core/qpack/qpack_encoder_test_utils.h"
+#include "net/third_party/quic/core/quic_epoll_connection_helper.h"
+#include "net/third_party/quic/core/quic_error_codes.h"
+#include "net/third_party/quic/core/quic_framer.h"
+>>>>>>>
 #include "net/third_party/quiche/src/quic/core/crypto/null_encrypter.h"
 #include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream.h"
 #include "net/third_party/quiche/src/quic/core/quic_epoll_connection_helper.h"
@@ -1368,8 +1383,17 @@
   headers["key3"] = std::string(15 * 1024, 'a');
 
   client_->SendCustomSynchronousRequest(headers, body);
-  EXPECT_EQ(QUIC_HEADERS_TOO_LARGE, client_->stream_error());
-  EXPECT_EQ(QUIC_NO_ERROR, client_->connection_error());
+
+  if (VersionUsesQpack(client_->client()
+                           ->client_session()
+                           ->connection()
+                           ->transport_version())) {
+    EXPECT_EQ(QUIC_HEADERS_STREAM_DATA_DECOMPRESS_FAILURE,
+              client_->connection_error());
+  } else {
+    EXPECT_EQ(QUIC_HEADERS_TOO_LARGE, client_->stream_error());
+    EXPECT_EQ(QUIC_NO_ERROR, client_->connection_error());
+  }
 }
 
 TEST_P(EndToEndTest, EarlyResponseWithQuicStreamNoError) {
@@ -2007,10 +2031,9 @@
       client_->client()->client_session());
   // In v47 and later, the crypto handshake (sent in CRYPTO frames) is not
   // subject to flow control.
-  if (!QuicVersionUsesCryptoFrames(client_->client()
-                                       ->client_session()
-                                       ->connection()
-                                       ->transport_version())) {
+  const QuicTransportVersion transport_version =
+      client_->client()->client_session()->connection()->transport_version();
+  if (!QuicVersionUsesCryptoFrames(transport_version)) {
     EXPECT_LT(QuicFlowControllerPeer::SendWindowSize(
                   crypto_stream->flow_controller()),
               kStreamIFCW);
@@ -2023,6 +2046,11 @@
   // has not been affected.
   EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
 
+  // No headers stream in IETF QUIC.
+  if (VersionUsesQpack(transport_version)) {
+    return;
+  }
+
   QuicHeadersStream* headers_stream = QuicSpdySessionPeer::GetHeadersStream(
       client_->client()->client_session());
   EXPECT_LT(
@@ -2187,6 +2215,29 @@
 
   client_->SendMessage(headers, "", /*fin=*/false);
 
+  // Size of headers on the request stream.  Zero if headers are sent on the
+  // header stream.
+  size_t header_size = 0;
+  if (VersionUsesQpack(client_->client()
+                           ->client_session()
+                           ->connection()
+                           ->transport_version())) {
+    // Determine size of compressed headers.
+    NoopDecoderStreamErrorDelegate decoder_stream_error_delegate;
+    NoopEncoderStreamSenderDelegate encoder_stream_sender_delegate;
+    QpackEncoder qpack_encoder(&decoder_stream_error_delegate,
+                               &encoder_stream_sender_delegate);
+    auto progressive_encoder =
+        qpack_encoder.EncodeHeaderList(/* stream_id = */ 0, &headers);
+    std::string encoded_headers;
+    while (progressive_encoder->HasNext()) {
+      progressive_encoder->Next(
+          /* max_encoded_bytes = */ std::numeric_limits<size_t>::max(),
+          &encoded_headers);
+    }
+    header_size = encoded_headers.size();
+  }
+
   // Test the AckNotifier's ability to track multiple packets by making the
   // request body exceed the size of a single packet.
   std::string request_string = "a request body bigger than one packet" +
@@ -2194,7 +2245,7 @@
 
   // The TestAckListener will cause a failure if not notified.
   QuicReferenceCountedPointer<TestAckListener> ack_listener(
-      new TestAckListener(request_string.length()));
+      new TestAckListener(header_size + request_string.length()));
 
   // Send the request, and register the delegate for ACKs.
   client_->SendData(request_string, true, ack_listener);
diff --git a/quic/core/http/quic_headers_stream_test.cc b/quic/core/http/quic_headers_stream_test.cc
index 2f669df..a41235f 100644
--- a/quic/core/http/quic_headers_stream_test.cc
+++ b/quic/core/http/quic_headers_stream_test.cc
@@ -370,6 +370,10 @@
 }
 
 TEST_P(QuicHeadersStreamTest, WriteHeaders) {
+  if (VersionUsesQpack(transport_version())) {
+    return;
+  }
+
   for (QuicStreamId stream_id = client_id_1_; stream_id < client_id_3_;
        stream_id += next_stream_id_) {
     for (bool fin : {false, true}) {
@@ -421,6 +425,10 @@
 }
 
 TEST_P(QuicHeadersStreamTest, ProcessRawData) {
+  if (VersionUsesQpack(transport_version())) {
+    return;
+  }
+
   for (QuicStreamId stream_id = client_id_1_; stream_id < client_id_3_;
        stream_id += next_stream_id_) {
     for (bool fin : {false, true}) {
@@ -536,6 +544,10 @@
 }
 
 TEST_P(QuicHeadersStreamTest, ProcessLargeRawData) {
+  if (VersionUsesQpack(transport_version())) {
+    return;
+  }
+
   QuicSpdySessionPeer::SetMaxUncompressedHeaderBytes(&session_, 256 * 1024);
   // We want to create a frame that is more than the SPDY Framer's max control
   // frame size, which is 16K, but less than the HPACK decoders max decode
@@ -710,6 +722,10 @@
 }
 
 TEST_P(QuicHeadersStreamTest, HpackDecoderDebugVisitor) {
+  if (VersionUsesQpack(transport_version())) {
+    return;
+  }
+
   auto hpack_decoder_visitor =
       QuicMakeUnique<StrictMock<MockQuicHpackDebugVisitor>>();
   {
@@ -762,6 +778,10 @@
 }
 
 TEST_P(QuicHeadersStreamTest, HpackEncoderDebugVisitor) {
+  if (VersionUsesQpack(transport_version())) {
+    return;
+  }
+
   auto hpack_encoder_visitor =
       QuicMakeUnique<StrictMock<MockQuicHpackDebugVisitor>>();
   if (perspective() == Perspective::IS_SERVER) {
diff --git a/quic/core/http/quic_spdy_client_stream_test.cc b/quic/core/http/quic_spdy_client_stream_test.cc
index 007bba0..5f5d5c1 100644
--- a/quic/core/http/quic_spdy_client_stream_test.cc
+++ b/quic/core/http/quic_spdy_client_stream_test.cc
@@ -193,9 +193,14 @@
   EXPECT_NE(QUIC_STREAM_NO_ERROR, stream_->stream_error());
 }
 
+// Test that receiving trailing headers (on the headers stream), containing a
+// final offset, results in the stream being closed at that byte offset.
 TEST_P(QuicSpdyClientStreamTest, ReceivingTrailers) {
-  // Test that receiving trailing headers, containing a final offset, results in
-  // the stream being closed at that byte offset.
+  // There is no kFinalOffsetHeaderKey if trailers are sent on the
+  // request/response stream.
+  if (VersionUsesQpack(connection_->transport_version())) {
+    return;
+  }
 
   // Send headers as usual.
   auto headers = AsHeaderList(headers_);
diff --git a/quic/core/http/quic_spdy_session.cc b/quic/core/http/quic_spdy_session.cc
index bdaa433..93d10e1 100644
--- a/quic/core/http/quic_spdy_session.cc
+++ b/quic/core/http/quic_spdy_session.cc
@@ -204,6 +204,12 @@
       return;
     }
 
+    if (VersionUsesQpack(session_->connection()->transport_version())) {
+      CloseConnection("HEADERS frame not allowed on headers stream.",
+                      QUIC_INVALID_HEADERS_STREAM_DATA);
+      return;
+    }
+
     // TODO(mpw): avoid down-conversion and plumb SpdyStreamPrecedence through
     // QuicHeadersStream.
     SpdyPriority priority =
@@ -399,7 +405,6 @@
   DCHECK(VersionUsesQpack(connection()->transport_version()));
 
   // TODO(112770235): Send decoder stream data on decoder stream.
-  QUIC_NOTREACHED();
 }
 
 void QuicSpdySession::OnStreamHeadersPriority(QuicStreamId stream_id,
@@ -472,6 +477,8 @@
     bool fin,
     SpdyPriority priority,
     QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
+  DCHECK(!VersionUsesQpack(connection()->transport_version()));
+
   return WriteHeadersOnHeadersStreamImpl(
       id, std::move(headers), fin,
       /* parent_stream_id = */ 0, Spdy3PriorityToHttp2Weight(priority),
@@ -568,6 +575,8 @@
     int weight,
     bool exclusive,
     QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
+  DCHECK(!VersionUsesQpack(connection()->transport_version()));
+
   SpdyHeadersIR headers_frame(id, std::move(headers));
   headers_frame.set_fin(fin);
   if (perspective() == Perspective::IS_CLIENT) {
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index 0e2fcf8..1a33fd1 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -1221,6 +1221,13 @@
   if (QuicVersionUsesCryptoFrames(connection_->transport_version())) {
     return;
   }
+
+  // This test depends on the headers stream, which does not exist when QPACK is
+  // used.
+  if (VersionUsesQpack(transport_version())) {
+    return;
+  }
+
   // Test that if the header stream is flow control blocked, then if the SHLO
   // contains a larger send window offset, the stream becomes unblocked.
   session_.set_writev_consumes_all_data(true);
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc
index d04efc5..2f161ae 100644
--- a/quic/core/http/quic_spdy_stream.cc
+++ b/quic/core/http/quic_spdy_stream.cc
@@ -4,11 +4,22 @@
 
 #include "net/third_party/quiche/src/quic/core/http/quic_spdy_stream.h"
 
+#include <limits>
 #include <string>
 #include <utility>
 
 #include "net/third_party/quiche/src/quic/core/http/quic_spdy_session.h"
 #include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_decoded_headers_accumulator.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_decoder.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_encoder.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_versions.h"
+#include "net/third_party/quiche/src/quic/core/quic_write_blocked_list.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flag_utils.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_session.h"
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
 #include "net/third_party/quiche/src/quic/core/quic_utils.h"
 #include "net/third_party/quiche/src/quic/core/quic_write_blocked_list.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
@@ -139,8 +150,11 @@
                                StreamType type)
     : QuicStream(id, spdy_session, /*is_static=*/false, type),
       spdy_session_(spdy_session),
+      on_body_available_called_because_sequencer_is_closed_(false),
       visitor_(nullptr),
       headers_decompressed_(false),
+      headers_length_(0, 0),
+      trailers_length_(0, 0),
       trailers_decompressed_(false),
       trailers_consumed_(false),
       http_decoder_visitor_(new HttpDecoderVisitor(this)),
@@ -148,9 +162,11 @@
       ack_listener_(nullptr) {
   DCHECK(!QuicUtils::IsCryptoStreamId(
       spdy_session->connection()->transport_version(), id));
-  // Don't receive any callbacks from the sequencer until headers
-  // are complete.
-  sequencer()->SetBlockedUntilFlush();
+  // If headers are sent on the headers stream, then do not receive any
+  // callbacks from the sequencer until headers are complete.
+  if (!VersionUsesQpack(spdy_session_->connection()->transport_version())) {
+    sequencer()->SetBlockedUntilFlush();
+  }
 
   if (VersionHasDataFrameHeader(
           spdy_session_->connection()->transport_version())) {
@@ -164,8 +180,11 @@
                                StreamType type)
     : QuicStream(std::move(pending), type, /*is_static=*/false),
       spdy_session_(spdy_session),
+      on_body_available_called_because_sequencer_is_closed_(false),
       visitor_(nullptr),
       headers_decompressed_(false),
+      headers_length_(0, 0),
+      trailers_length_(0, 0),
       trailers_decompressed_(false),
       trailers_consumed_(false),
       http_decoder_visitor_(new HttpDecoderVisitor(this)),
@@ -173,9 +192,11 @@
       ack_listener_(nullptr) {
   DCHECK(!QuicUtils::IsCryptoStreamId(
       spdy_session->connection()->transport_version(), id()));
-  // Don't receive any callbacks from the sequencer until headers
-  // are complete.
-  sequencer()->SetBlockedUntilFlush();
+  // If headers are sent on the headers stream, then do not receive any
+  // callbacks from the sequencer until headers are complete.
+  if (!VersionUsesQpack(spdy_session_->connection()->transport_version())) {
+    sequencer()->SetBlockedUntilFlush();
+  }
 
   if (VersionHasDataFrameHeader(
           spdy_session_->connection()->transport_version())) {
@@ -192,7 +213,11 @@
     QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
   size_t bytes_written =
       WriteHeadersImpl(std::move(header_block), fin, std::move(ack_listener));
-  if (fin) {
+  if (!VersionUsesQpack(spdy_session_->connection()->transport_version()) &&
+      fin) {
+    // If HEADERS are sent on the headers stream, then |fin_sent_| needs to be
+    // set and write side needs to be closed without actually sending a FIN on
+    // this stream.
     // TODO(rch): Add test to ensure fin_sent_ is set whenever a fin is sent.
     set_fin_sent(true);
     CloseWriteSide();
@@ -217,13 +242,15 @@
   unacked_frame_headers_offsets_.Add(
       send_buffer().stream_offset(),
       send_buffer().stream_offset() + header_length);
-  QUIC_DLOG(INFO) << "Stream " << id() << " is writing header of length "
+  QUIC_DLOG(INFO) << "Stream " << id()
+                  << " is writing DATA frame header of length "
                   << header_length;
   WriteOrBufferData(QuicStringPiece(buffer.get(), header_length), false,
                     nullptr);
 
   // Write body.
-  QUIC_DLOG(INFO) << "Stream " << id() << " is writing body of length "
+  QUIC_DLOG(INFO) << "Stream " << id()
+                  << " is writing DATA frame payload of length "
                   << data.length();
   WriteOrBufferData(data, fin, nullptr);
 }
@@ -236,26 +263,33 @@
     return 0;
   }
 
-  // The header block must contain the final offset for this stream, as the
-  // trailers may be processed out of order at the peer.
-  QUIC_DLOG(INFO) << "Inserting trailer: (" << kFinalOffsetHeaderKey << ", "
-                  << stream_bytes_written() + BufferedDataBytes() << ")";
-  trailer_block.insert(
-      std::make_pair(kFinalOffsetHeaderKey,
-                     QuicTextUtils::Uint64ToString(stream_bytes_written() +
-                                                   BufferedDataBytes())));
+  if (!VersionUsesQpack(spdy_session_->connection()->transport_version())) {
+    // The header block must contain the final offset for this stream, as the
+    // trailers may be processed out of order at the peer.
+    const QuicStreamOffset final_offset =
+        stream_bytes_written() + BufferedDataBytes();
+    QUIC_DLOG(INFO) << "Inserting trailer: (" << kFinalOffsetHeaderKey << ", "
+                    << final_offset << ")";
+    trailer_block.insert(std::make_pair(
+        kFinalOffsetHeaderKey, QuicTextUtils::Uint64ToString(final_offset)));
+  }
 
   // Write the trailing headers with a FIN, and close stream for writing:
   // trailers are the last thing to be sent on a stream.
   const bool kFin = true;
   size_t bytes_written =
       WriteHeadersImpl(std::move(trailer_block), kFin, std::move(ack_listener));
-  set_fin_sent(kFin);
 
-  // Trailers are the last thing to be sent on a stream, but if there is still
-  // queued data then CloseWriteSide() will cause it never to be sent.
-  if (BufferedDataBytes() == 0) {
-    CloseWriteSide();
+  // If trailers are sent on the headers stream, then |fin_sent_| needs to be
+  // set without actually sending a FIN on this stream.
+  if (!VersionUsesQpack(spdy_session_->connection()->transport_version())) {
+    set_fin_sent(kFin);
+
+    // Also, write side of this stream needs to be closed.  However, only do
+    // this if there is no more buffered data, otherwise it will never be sent.
+    if (BufferedDataBytes() == 0) {
+      CloseWriteSide();
+    }
   }
 
   return bytes_written;
@@ -298,12 +332,14 @@
   unacked_frame_headers_offsets_.Add(
       send_buffer().stream_offset(),
       send_buffer().stream_offset() + header_length);
-  QUIC_DLOG(INFO) << "Stream " << id() << " is writing header of length "
+  QUIC_DLOG(INFO) << "Stream " << id()
+                  << " is writing DATA frame header of length "
                   << header_length;
   WriteMemSlices(storage.ToSpan(), false);
 
   // Write body.
-  QUIC_DLOG(INFO) << "Stream " << id() << " is writing body of length "
+  QUIC_DLOG(INFO) << "Stream " << id()
+                  << " is writing DATA frame payload of length "
                   << slices.total_length();
   return WriteMemSlices(slices, fin);
 }
@@ -352,6 +388,15 @@
 }
 
 void QuicSpdyStream::MarkTrailersConsumed() {
+  /*if (VersionUsesQpack(spdy_session_->connection()->transport_version()) &&
+      !reading_stopped()) {
+    const QuicByteCount trailers_total_length =
+        trailers_length_.header_length + trailers_length_.payload_length;
+    if (trailers_total_length > 0) {
+      sequencer()->MarkConsumed(trailers_total_length);
+    }
+  }*/
+
   trailers_consumed_ = true;
 }
 
@@ -365,8 +410,35 @@
 
 void QuicSpdyStream::ConsumeHeaderList() {
   header_list_.Clear();
-  if (FinishedReadingHeaders()) {
-    sequencer()->SetUnblocked();
+
+  if (!VersionUsesQpack(spdy_session_->connection()->transport_version())) {
+    if (FinishedReadingHeaders()) {
+      sequencer()->SetUnblocked();
+    }
+    return;
+  }
+
+  /*if (!reading_stopped()) {
+    const QuicByteCount headers_total_length =
+        headers_length_.header_length + headers_length_.payload_length;
+    if (headers_total_length > 0) {
+      sequencer()->MarkConsumed(headers_total_length);
+    }
+  }*/
+
+  if (!FinishedReadingHeaders()) {
+    return;
+  }
+
+  if (body_buffer_.HasBytesToRead()) {
+    OnBodyAvailable();
+    return;
+  }
+
+  if (sequencer()->IsClosed() &&
+      !on_body_available_called_because_sequencer_is_closed_) {
+    on_body_available_called_because_sequencer_is_closed_ = true;
+    OnBodyAvailable();
   }
 }
 
@@ -398,7 +470,15 @@
 }
 
 void QuicSpdyStream::OnHeadersTooLarge() {
-  Reset(QUIC_HEADERS_TOO_LARGE);
+  if (VersionUsesQpack(spdy_session_->connection()->transport_version())) {
+    // TODO(124216424): Use HTTP_EXCESSIVE_LOAD error code.
+    std::string error_message =
+        QuicStrCat("Too large headers received on stream ", id());
+    CloseConnectionWithDetails(QUIC_HEADERS_STREAM_DATA_DECOMPRESS_FAILURE,
+                               error_message);
+  } else {
+    Reset(QUIC_HEADERS_TOO_LARGE);
+  }
 }
 
 void QuicSpdyStream::OnInitialHeadersComplete(
@@ -407,8 +487,20 @@
     const QuicHeaderList& header_list) {
   headers_decompressed_ = true;
   header_list_ = header_list;
+
+  if (VersionUsesQpack(spdy_session_->connection()->transport_version())) {
+    if (fin) {
+      OnStreamFrame(
+          QuicStreamFrame(id(), /* fin = */ true,
+                          flow_controller()->highest_received_byte_offset(),
+                          QuicStringPiece()));
+    }
+    return;
+  }
+
   if (fin) {
-    OnStreamFrame(QuicStreamFrame(id(), fin, 0, QuicStringPiece()));
+    OnStreamFrame(
+        QuicStreamFrame(id(), fin, /* offset = */ 0, QuicStringPiece()));
   }
   if (FinishedReadingHeaders()) {
     sequencer()->SetUnblocked();
@@ -431,15 +523,18 @@
     size_t /*frame_len*/,
     const QuicHeaderList& header_list) {
   DCHECK(!trailers_decompressed_);
-  if (fin_received()) {
-    QUIC_DLOG(ERROR) << "Received Trailers after FIN, on stream: " << id();
+  if (!VersionUsesQpack(spdy_session_->connection()->transport_version()) &&
+      fin_received()) {
+    QUIC_DLOG(INFO) << "Received Trailers after FIN, on stream: " << id();
     session()->connection()->CloseConnection(
         QUIC_INVALID_HEADERS_STREAM_DATA, "Trailers after fin",
         ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
     return;
   }
-  if (!fin) {
-    QUIC_DLOG(ERROR) << "Trailers must have FIN set, on stream: " << id();
+
+  if (!VersionUsesQpack(spdy_session_->connection()->transport_version()) &&
+      !fin) {
+    QUIC_DLOG(INFO) << "Trailers must have FIN set, on stream: " << id();
     session()->connection()->CloseConnection(
         QUIC_INVALID_HEADERS_STREAM_DATA, "Fin missing from trailers",
         ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
@@ -447,8 +542,9 @@
   }
 
   size_t final_byte_offset = 0;
-  if (!SpdyUtils::CopyAndValidateTrailers(header_list,
-                                          /* expect_final_byte_offset = */ true,
+  const bool expect_final_byte_offset =
+      !VersionUsesQpack(spdy_session_->connection()->transport_version());
+  if (!SpdyUtils::CopyAndValidateTrailers(header_list, expect_final_byte_offset,
                                           &final_byte_offset,
                                           &received_trailers_)) {
     QUIC_DLOG(ERROR) << "Trailers for stream " << id() << " are malformed.";
@@ -458,16 +554,12 @@
     return;
   }
   trailers_decompressed_ = true;
+  const QuicStreamOffset offset =
+      VersionUsesQpack(spdy_session_->connection()->transport_version())
+          ? flow_controller()->highest_received_byte_offset()
+          : final_byte_offset;
   OnStreamFrame(
-      QuicStreamFrame(id(), fin, final_byte_offset, QuicStringPiece()));
-}
-
-size_t QuicSpdyStream::WriteHeadersImpl(
-    spdy::SpdyHeaderBlock header_block,
-    bool fin,
-    QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
-  return spdy_session_->WriteHeadersOnHeadersStream(
-      id(), std::move(header_block), fin, priority(), std::move(ack_listener));
+      QuicStreamFrame(id(), /* fin = */ true, offset, QuicStringPiece()));
 }
 
 void QuicSpdyStream::OnPriorityFrame(SpdyPriority priority) {
@@ -488,7 +580,10 @@
 }
 
 void QuicSpdyStream::OnDataAvailable() {
-  DCHECK(FinishedReadingHeaders());
+  if (!VersionUsesQpack(spdy_session_->connection()->transport_version())) {
+    // Sequencer must be blocked until headers are consumed.
+    DCHECK(FinishedReadingHeaders());
+  }
 
   if (!VersionHasDataFrameHeader(
           session()->connection()->transport_version())) {
@@ -502,14 +597,20 @@
                           iov.iov_len);
   }
 
+  // Do not call OnBodyAvailable() until headers are consumed.
+  if (!FinishedReadingHeaders()) {
+    return;
+  }
+
   if (body_buffer_.HasBytesToRead()) {
     OnBodyAvailable();
     return;
   }
 
-  if (sequencer()->IsClosed()) {
+  if (sequencer()->IsClosed() &&
+      !on_body_available_called_because_sequencer_is_closed_) {
+    on_body_available_called_because_sequencer_is_closed_ = true;
     OnBodyAvailable();
-    return;
   }
 }
 
@@ -645,14 +746,98 @@
 
 void QuicSpdyStream::OnHeadersFrameStart(Http3FrameLengths frame_length) {
   DCHECK(VersionUsesQpack(spdy_session_->connection()->transport_version()));
+  DCHECK(!qpack_decoded_headers_accumulator_);
+
+  if (headers_decompressed_) {
+    trailers_length_ = frame_length;
+  } else {
+    headers_length_ = frame_length;
+  }
+
+  qpack_decoded_headers_accumulator_ =
+      QuicMakeUnique<QpackDecodedHeadersAccumulator>(
+          id(), spdy_session_->qpack_decoder(),
+          spdy_session_->max_inbound_header_list_size());
+  sequencer()->MarkConsumed(frame_length.header_length);
 }
 
 void QuicSpdyStream::OnHeadersFramePayload(QuicStringPiece payload) {
   DCHECK(VersionUsesQpack(spdy_session_->connection()->transport_version()));
+
+  if (!qpack_decoded_headers_accumulator_->Decode(payload)) {
+    // TODO(124216424): Use HTTP_QPACK_DECOMPRESSION_FAILED error code.
+    std::string error_message =
+        QuicStrCat("Error decompressing header block on stream ", id(), ": ",
+                   qpack_decoded_headers_accumulator_->error_message());
+    CloseConnectionWithDetails(QUIC_DECOMPRESSION_FAILURE, error_message);
+    return;
+  }
+  sequencer()->MarkConsumed(payload.size());
 }
 
 void QuicSpdyStream::OnHeadersFrameEnd() {
   DCHECK(VersionUsesQpack(spdy_session_->connection()->transport_version()));
+
+  if (!qpack_decoded_headers_accumulator_->EndHeaderBlock()) {
+    // TODO(124216424): Use HTTP_QPACK_DECOMPRESSION_FAILED error code.
+    std::string error_message =
+        QuicStrCat("Error decompressing header block on stream ", id(), ": ",
+                   qpack_decoded_headers_accumulator_->error_message());
+    CloseConnectionWithDetails(QUIC_DECOMPRESSION_FAILURE, error_message);
+    return;
+  }
+
+  const QuicByteCount frame_length = headers_decompressed_
+                                         ? trailers_length_.payload_length
+                                         : headers_length_.payload_length;
+  OnStreamHeaderList(/* fin = */ false, frame_length,
+                     qpack_decoded_headers_accumulator_->quic_header_list());
+
+  qpack_decoded_headers_accumulator_.reset();
+}
+
+size_t QuicSpdyStream::WriteHeadersImpl(
+    spdy::SpdyHeaderBlock header_block,
+    bool fin,
+    QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
+  if (!VersionUsesQpack(spdy_session_->connection()->transport_version())) {
+    return spdy_session_->WriteHeadersOnHeadersStream(
+        id(), std::move(header_block), fin, priority(),
+        std::move(ack_listener));
+  }
+
+  // Encode header list.
+  auto progressive_encoder = spdy_session_->qpack_encoder()->EncodeHeaderList(
+      /* stream_id = */ id(), &header_block);
+  std::string encoded_headers;
+  while (progressive_encoder->HasNext()) {
+    progressive_encoder->Next(
+        /* max_encoded_bytes = */ std::numeric_limits<size_t>::max(),
+        &encoded_headers);
+  }
+
+  // Write HEADERS frame.
+  std::unique_ptr<char[]> headers_frame_header;
+  const size_t headers_frame_header_length =
+      encoder_.SerializeHeadersFrameHeader(encoded_headers.size(),
+                                           &headers_frame_header);
+  unacked_frame_headers_offsets_.Add(
+      send_buffer().stream_offset(),
+      send_buffer().stream_offset() + headers_frame_header_length);
+
+  QUIC_DLOG(INFO) << "Stream " << id()
+                  << " is writing HEADERS frame header of length "
+                  << headers_frame_header_length;
+  WriteOrBufferData(
+      QuicStringPiece(headers_frame_header.get(), headers_frame_header_length),
+      /* fin = */ false, /* ack_listener = */ nullptr);
+
+  QUIC_DLOG(INFO) << "Stream " << id()
+                  << " is writing HEADERS frame payload of length "
+                  << encoded_headers.length();
+  WriteOrBufferData(encoded_headers, fin, nullptr);
+
+  return encoded_headers.size();
 }
 
 #undef ENDPOINT  // undef for jumbo builds
diff --git a/quic/core/http/quic_spdy_stream.h b/quic/core/http/quic_spdy_stream.h
index f81a421..3c5a166 100644
--- a/quic/core/http/quic_spdy_stream.h
+++ b/quic/core/http/quic_spdy_stream.h
@@ -34,6 +34,7 @@
 class QuicStreamPeer;
 }  // namespace test
 
+class QpackDecodedHeadersAccumulator;
 class QuicSpdySession;
 
 // A QUIC stream that can send and receive HTTP2 (SPDY) headers.
@@ -105,8 +106,8 @@
   // Called in OnDataAvailable() after it finishes the decoding job.
   virtual void OnBodyAvailable() = 0;
 
-  // Writes the headers contained in |header_block| to the dedicated
-  // headers stream.
+  // Writes the headers contained in |header_block| on the dedicated headers
+  // stream or on this stream, depending on VersionUsesQpack().
   virtual size_t WriteHeaders(
       spdy::SpdyHeaderBlock header_block,
       bool fin,
@@ -115,8 +116,9 @@
   // Sends |data| to the peer, or buffers if it can't be sent immediately.
   void WriteOrBufferBody(QuicStringPiece data, bool fin);
 
-  // Writes the trailers contained in |trailer_block| to the dedicated
-  // headers stream. Trailers will always have the FIN set.
+  // Writes the trailers contained in |trailer_block| on the dedicated headers
+  // stream or on this stream, depending on VersionUsesQpack().  Trailers will
+  // always have the FIN flag set.
   virtual size_t WriteTrailers(
       spdy::SpdyHeaderBlock trailer_block,
       QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener);
@@ -247,12 +249,18 @@
 
   QuicSpdySession* spdy_session_;
 
+  bool on_body_available_called_because_sequencer_is_closed_;
+
   Visitor* visitor_;
   // True if the headers have been completely decompressed.
   bool headers_decompressed_;
   // Contains a copy of the decompressed header (name, value) pairs until they
   // are consumed via Readv.
   QuicHeaderList header_list_;
+  // Length of HEADERS frame, including frame header and payload.
+  Http3FrameLengths headers_length_;
+  // Length of TRAILERS frame, including frame header and payload.
+  Http3FrameLengths trailers_length_;
 
   // True if the trailers have been completely decompressed.
   bool trailers_decompressed_;
@@ -265,6 +273,9 @@
   HttpEncoder encoder_;
   // Http decoder for processing raw incoming stream frames.
   HttpDecoder decoder_;
+  // Headers accumulator for decoding HEADERS frame payload.
+  std::unique_ptr<QpackDecodedHeadersAccumulator>
+      qpack_decoded_headers_accumulator_;
   // Visitor of the HttpDecoder.
   std::unique_ptr<HttpDecoderVisitor> http_decoder_visitor_;
   // Buffer that contains decoded data of the stream.
diff --git a/quic/core/http/quic_spdy_stream_test.cc b/quic/core/http/quic_spdy_stream_test.cc
index c45a899..eb9a0b3 100644
--- a/quic/core/http/quic_spdy_stream_test.cc
+++ b/quic/core/http/quic_spdy_stream_test.cc
@@ -78,6 +78,11 @@
                               ack_listener) override {
     saved_headers_ = std::move(header_block);
     WriteHeadersMock(fin);
+    if (VersionUsesQpack(transport_version())) {
+      // In this case, call QuicSpdyStream::WriteHeadersImpl() that does the
+      // actual work of closing the stream.
+      QuicSpdyStream::WriteHeadersImpl(saved_headers_.Clone(), fin, nullptr);
+    }
     return 0;
   }
 
@@ -212,10 +217,23 @@
   QuicHeaderList headers;
   stream_->OnStreamHeadersPriority(kV3HighestPriority);
 
-  EXPECT_CALL(*session_,
-              SendRstStream(stream_->id(), QUIC_HEADERS_TOO_LARGE, 0));
+  const bool version_uses_qpack =
+      VersionUsesQpack(connection_->transport_version());
+
+  if (version_uses_qpack) {
+    EXPECT_CALL(*connection_,
+                CloseConnection(QUIC_HEADERS_STREAM_DATA_DECOMPRESS_FAILURE,
+                                "Too large headers received on stream 4", _));
+  } else {
+    EXPECT_CALL(*session_,
+                SendRstStream(stream_->id(), QUIC_HEADERS_TOO_LARGE, 0));
+  }
+
   stream_->OnStreamHeaderList(false, 1 << 20, headers);
-  EXPECT_EQ(QUIC_HEADERS_TOO_LARGE, stream_->stream_error());
+
+  if (!version_uses_qpack) {
+    EXPECT_EQ(QUIC_HEADERS_TOO_LARGE, stream_->stream_error());
+  }
 }
 
 TEST_P(QuicSpdyStreamTest, ProcessHeaderListWithFin) {
@@ -919,7 +937,11 @@
   trailers_block["key2"] = "value2";
   trailers_block["key3"] = "value3";
   SpdyHeaderBlock trailers_block_with_final_offset = trailers_block.Clone();
-  trailers_block_with_final_offset[kFinalOffsetHeaderKey] = "0";
+  if (!VersionUsesQpack(GetParam().transport_version)) {
+    // :final-offset pseudo-header is only added if trailers are sent
+    // on the headers stream.
+    trailers_block_with_final_offset[kFinalOffsetHeaderKey] = "0";
+  }
   total_bytes = 0;
   QuicHeaderList trailers;
   for (const auto& p : trailers_block_with_final_offset) {
@@ -943,6 +965,12 @@
   // body, stream is closed at the right offset.
   Initialize(kShouldProcessData);
 
+  // kFinalOffsetHeaderKey is not used when HEADERS are sent on the
+  // request/response stream.
+  if (VersionUsesQpack(GetParam().transport_version)) {
+    return;
+  }
+
   // Receive initial headers.
   QuicHeaderList headers = ProcessHeaders(false, headers_);
   stream_->ConsumeHeaderList();
@@ -988,6 +1016,12 @@
   // Test that receiving trailers without a final offset field is an error.
   Initialize(kShouldProcessData);
 
+  // kFinalOffsetHeaderKey is not used when HEADERS are sent on the
+  // request/response stream.
+  if (VersionUsesQpack(GetParam().transport_version)) {
+    return;
+  }
+
   // Receive initial headers.
   ProcessHeaders(false, headers_);
   stream_->ConsumeHeaderList();
@@ -1010,10 +1044,60 @@
                               trailers.uncompressed_header_bytes(), trailers);
 }
 
+TEST_P(QuicSpdyStreamTest, ReceivingTrailersOnRequestStream) {
+  Initialize(kShouldProcessData);
+
+  if (!VersionUsesQpack(GetParam().transport_version)) {
+    return;
+  }
+
+  // Receive initial headers.
+  QuicHeaderList headers = ProcessHeaders(false, headers_);
+  stream_->ConsumeHeaderList();
+
+  const std::string body = "this is the body";
+  std::unique_ptr<char[]> buf;
+  QuicByteCount header_length =
+      encoder_.SerializeDataFrameHeader(body.length(), &buf);
+  std::string header = std::string(buf.get(), header_length);
+  std::string data = HasFrameHeader() ? header + body : body;
+
+  // Receive trailing headers.
+  SpdyHeaderBlock trailers_block;
+  trailers_block["key1"] = "value1";
+  trailers_block["key2"] = "value2";
+  trailers_block["key3"] = "value3";
+
+  QuicHeaderList trailers = ProcessHeaders(true, trailers_block);
+
+  // The trailers should be decompressed, and readable from the stream.
+  EXPECT_TRUE(stream_->trailers_decompressed());
+
+  EXPECT_EQ(trailers_block, stream_->received_trailers());
+
+  // Consuming the trailers erases them from the stream.
+  stream_->MarkTrailersConsumed();
+  EXPECT_TRUE(stream_->FinishedReadingTrailers());
+  EXPECT_TRUE(stream_->IsDoneReading());
+
+  // Receive and consume body.
+  QuicStreamFrame frame(GetNthClientInitiatedBidirectionalId(0), /*fin=*/false,
+                        0, data);
+  stream_->OnStreamFrame(frame);
+  EXPECT_EQ(body, stream_->data());
+  EXPECT_TRUE(stream_->IsDoneReading());
+}
+
 TEST_P(QuicSpdyStreamTest, ReceivingTrailersWithoutFin) {
   // Test that received Trailers must always have the FIN set.
   Initialize(kShouldProcessData);
 
+  // In IETF QUIC, there is no such thing as FIN flag on HTTP/3 frames like the
+  // HEADERS frame.
+  if (VersionUsesQpack(GetParam().transport_version)) {
+    return;
+  }
+
   // Receive initial headers.
   auto headers = AsHeaderList(headers_);
   stream_->OnStreamHeaderList(/*fin=*/false,
@@ -1053,6 +1137,13 @@
   // If body data are received with a FIN, no trailers should then arrive.
   Initialize(kShouldProcessData);
 
+  // If HEADERS frames are sent on the request/response stream,
+  // then the sequencer will block them from reaching QuicSpdyStream
+  // after the stream is closed.
+  if (VersionUsesQpack(GetParam().transport_version)) {
+    return;
+  }
+
   // Receive initial headers without FIN set.
   ProcessHeaders(false, headers_);
   stream_->ConsumeHeaderList();
@@ -1101,6 +1192,12 @@
   // to be sent on a stream.
   Initialize(kShouldProcessData);
 
+  if (VersionUsesQpack(GetParam().transport_version)) {
+    // In this case, TestStream::WriteHeadersImpl() does not prevent writes.
+    EXPECT_CALL(*session_, WritevData(stream_, stream_->id(), _, _, _))
+        .Times(AtLeast(1));
+  }
+
   // Write the initial headers, without a FIN.
   EXPECT_CALL(*stream_, WriteHeadersMock(false));
   stream_->WriteHeaders(SpdyHeaderBlock(), /*fin=*/false, nullptr);
@@ -1118,6 +1215,12 @@
   // peer contain the final offset field indicating last byte of data.
   Initialize(kShouldProcessData);
 
+  if (VersionUsesQpack(GetParam().transport_version)) {
+    // In this case, TestStream::WriteHeadersImpl() does not prevent writes.
+    EXPECT_CALL(*session_, WritevData(stream_, stream_->id(), _, _, _))
+        .Times(AtLeast(1));
+  }
+
   // Write the initial headers.
   EXPECT_CALL(*stream_, WriteHeadersMock(false));
   stream_->WriteHeaders(SpdyHeaderBlock(), /*fin=*/false, nullptr);
@@ -1137,12 +1240,18 @@
   // number of body bytes written (including queued bytes).
   SpdyHeaderBlock trailers;
   trailers["trailer key"] = "trailer value";
-  SpdyHeaderBlock trailers_with_offset(trailers.Clone());
-  trailers_with_offset[kFinalOffsetHeaderKey] =
-      QuicTextUtils::Uint64ToString(body.length() + header_length);
+
+  SpdyHeaderBlock expected_trailers(trailers.Clone());
+  // :final-offset pseudo-header is only added if trailers are sent
+  // on the headers stream.
+  if (!VersionUsesQpack(GetParam().transport_version)) {
+    expected_trailers[kFinalOffsetHeaderKey] =
+        QuicTextUtils::Uint64ToString(body.length() + header_length);
+  }
+
   EXPECT_CALL(*stream_, WriteHeadersMock(true));
   stream_->WriteTrailers(std::move(trailers), nullptr);
-  EXPECT_EQ(trailers_with_offset, stream_->saved_headers());
+  EXPECT_EQ(expected_trailers, stream_->saved_headers());
 }
 
 TEST_P(QuicSpdyStreamTest, WritingTrailersClosesWriteSide) {
@@ -1150,12 +1259,16 @@
   // (headers and body), that this closes the stream for writing.
   Initialize(kShouldProcessData);
 
+  // Expect data being written on the stream.  In addition to that, headers are
+  // also written on the stream in case of IETF QUIC.
+  EXPECT_CALL(*session_, WritevData(stream_, stream_->id(), _, _, _))
+      .Times(AtLeast(1));
+
   // Write the initial headers.
   EXPECT_CALL(*stream_, WriteHeadersMock(false));
   stream_->WriteHeaders(SpdyHeaderBlock(), /*fin=*/false, nullptr);
 
   // Write non-zero body data.
-  EXPECT_CALL(*session_, WritevData(_, _, _, _, _)).Times(AtLeast(1));
   const int kBodySize = 1 * 1024;  // 1 kB
   stream_->WriteOrBufferBody(std::string(kBodySize, 'x'), false);
   EXPECT_EQ(0u, stream_->BufferedDataBytes());
@@ -1168,6 +1281,13 @@
 }
 
 TEST_P(QuicSpdyStreamTest, WritingTrailersWithQueuedBytes) {
+  // This test exercises sending trailers on the headers stream while data is
+  // still queued on the response/request stream.  In IETF QUIC, data and
+  // trailers are sent on the same stream, so this test does not apply.
+  if (VersionUsesQpack(GetParam().transport_version)) {
+    return;
+  }
+
   // Test that the stream is not closed for writing when trailers are sent
   // while there are still body bytes queued.
   testing::InSequence seq;
@@ -1202,7 +1322,10 @@
 
 TEST_P(QuicSpdyStreamTest, WritingTrailersAfterFIN) {
   // EXPECT_QUIC_BUG tests are expensive so only run one instance of them.
-  if (GetParam() != AllSupportedVersions()[0]) {
+  // In IETF QUIC, there is no such thing as FIN flag on HTTP/3 frames like the
+  // HEADERS frame.  That is version 99, which is element 0 of the array, so
+  // pick another element.
+  if (GetParam() != AllSupportedVersions()[1]) {
     return;
   }
 
@@ -1554,6 +1677,65 @@
       QuicSpdyStreamPeer::unacked_frame_headers_offsets(stream_).Empty());
 }
 
+TEST_P(QuicSpdyStreamTest, HeadersFrameOnRequestStream) {
+  if (!VersionUsesQpack(GetParam().transport_version)) {
+    return;
+  }
+
+  Initialize(kShouldProcessData);
+
+  // QPACK encoded header block with single header field "foo: bar".
+  std::string headers_frame_payload =
+      QuicTextUtils::HexDecode("00002a94e703626172");
+  std::unique_ptr<char[]> headers_buffer;
+  QuicByteCount headers_frame_header_length =
+      encoder_.SerializeHeadersFrameHeader(headers_frame_payload.length(),
+                                           &headers_buffer);
+  QuicStringPiece headers_frame_header(headers_buffer.get(),
+                                       headers_frame_header_length);
+
+  std::string data_frame_payload = "some data";
+  std::unique_ptr<char[]> data_buffer;
+  QuicByteCount data_frame_header_length = encoder_.SerializeDataFrameHeader(
+      data_frame_payload.length(), &data_buffer);
+  QuicStringPiece data_frame_header(data_buffer.get(),
+                                    data_frame_header_length);
+
+  // QPACK encoded header block with single header field
+  // "custom-key: custom-value".
+  std::string trailers_frame_payload =
+      QuicTextUtils::HexDecode("00002f0125a849e95ba97d7f8925a849e95bb8e8b4bf");
+  std::unique_ptr<char[]> trailers_buffer;
+  QuicByteCount trailers_frame_header_length =
+      encoder_.SerializeHeadersFrameHeader(trailers_frame_payload.length(),
+                                           &trailers_buffer);
+  QuicStringPiece trailers_frame_header(trailers_buffer.get(),
+                                        trailers_frame_header_length);
+
+  std::string stream_frame_payload = QuicStrCat(
+      headers_frame_header, headers_frame_payload, data_frame_header,
+      data_frame_payload, trailers_frame_header, trailers_frame_payload);
+  QuicStreamFrame frame(stream_->id(), false, 0, stream_frame_payload);
+  stream_->OnStreamFrame(frame);
+
+  auto it = stream_->header_list().begin();
+  ASSERT_TRUE(it != stream_->header_list().end());
+  EXPECT_EQ("foo", it->first);
+  EXPECT_EQ("bar", it->second);
+  ++it;
+  EXPECT_TRUE(it == stream_->header_list().end());
+
+  // QuicSpdyStream only calls OnBodyAvailable()
+  // after the header list has been consumed.
+  EXPECT_EQ("", stream_->data());
+  stream_->ConsumeHeaderList();
+  EXPECT_EQ("some data", stream_->data());
+
+  const spdy::SpdyHeaderBlock& trailers = stream_->received_trailers();
+  EXPECT_THAT(trailers, testing::ElementsAre(
+                            testing::Pair("custom-key", "custom-value")));
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/quic_versions.h b/quic/core/quic_versions.h
index 1f6a431..02a0681 100644
--- a/quic/core/quic_versions.h
+++ b/quic/core/quic_versions.h
@@ -343,13 +343,23 @@
   return transport_version == QUIC_VERSION_99;
 }
 
-// Returns true if QuicSpdySession instantiates a QPACK encoder and decoder.
+// If true:
+// * QuicSpdySession instantiates a QPACK encoder and decoder;
+// * HEADERS frames (containing headers or trailers) are sent on
+//   request/response streams, compressed with QPACK;
+// * trailers must not contain :final-offset key.
+// If false:
+// * HEADERS frames (containing headers or trailers) are sent on the headers
+//   stream, compressed with HPACK;
+// * trailers must contain :final-offset key.
+//
 // TODO(123528590): Implement the following features and gate them on this
-// function as well, optionally renaming this function as appropriate.
-// Send HEADERS on the request/response stream instead of the headers stream.
-// Send PUSH_PROMISE on the request/response stream instead of headers stream.
-// Send PRIORITY on the request/response stream instead of the headers stream.
-// Do not instantiate the headers stream object.
+// function as well, optionally renaming this function as appropriate:
+// * send PUSH_PROMISE frames on the request/response stream instead of the
+//   headers stream;
+// * send PRIORITY frames on the request/response stream instead of the headers
+//   stream;
+// * do not instantiate the headers stream object.
 QUIC_EXPORT_PRIVATE inline bool VersionUsesQpack(
     QuicTransportVersion transport_version) {
   const bool uses_qpack = (transport_version == QUIC_VERSION_99);
diff --git a/quic/tools/quic_simple_server_session_test.cc b/quic/tools/quic_simple_server_session_test.cc
index aa9104a..e08af9a 100644
--- a/quic/tools/quic_simple_server_session_test.cc
+++ b/quic/tools/quic_simple_server_session_test.cc
@@ -46,7 +46,12 @@
 namespace quic {
 namespace test {
 namespace {
+
 typedef QuicSimpleServerSession::PromisedStreamInfo PromisedStreamInfo;
+
+const QuicByteCount kHeadersFrameHeaderLength = 2;
+const QuicByteCount kHeadersFramePayloadLength = 9;
+
 }  // namespace
 
 class QuicSimpleServerSessionPeer {
@@ -643,6 +648,16 @@
         // Since flow control window is smaller than response body, not the
         // whole body will be sent.
         QuicStreamOffset offset = 0;
+        if (VersionUsesQpack(connection_->transport_version())) {
+          EXPECT_CALL(*connection_,
+                      SendStreamData(stream_id, kHeadersFrameHeaderLength,
+                                     offset, NO_FIN));
+          offset += kHeadersFrameHeaderLength;
+          EXPECT_CALL(*connection_,
+                      SendStreamData(stream_id, kHeadersFramePayloadLength,
+                                     offset, NO_FIN));
+          offset += kHeadersFramePayloadLength;
+        }
         if (VersionHasDataFrameHeader(connection_->transport_version())) {
           EXPECT_CALL(*connection_,
                       SendStreamData(stream_id, data_frame_header_length,
@@ -661,11 +676,13 @@
     return data_frame_header_length;
   }
 
-  void ConsumeHeadersStreamData() {
-    QuicStreamId headers_stream_id =
-        QuicUtils::GetHeadersStreamId(connection_->transport_version());
-    EXPECT_CALL(*connection_, SendStreamData(headers_stream_id, _, _, _))
-        .Times(AtLeast(1));
+  void MaybeConsumeHeadersStreamData() {
+    if (!VersionUsesQpack(connection_->transport_version())) {
+      QuicStreamId headers_stream_id =
+          QuicUtils::GetHeadersStreamId(connection_->transport_version());
+      EXPECT_CALL(*connection_, SendStreamData(headers_stream_id, _, _, _))
+          .Times(AtLeast(1));
+    }
   }
 };
 
@@ -677,7 +694,7 @@
 // PUSH_PROMISE's will be sent out and only kMaxStreamsForTest streams will be
 // opened and send push response.
 TEST_P(QuicSimpleServerSessionServerPushTest, TestPromisePushResources) {
-  ConsumeHeadersStreamData();
+  MaybeConsumeHeadersStreamData();
   size_t num_resources = kMaxStreamsForTest + 5;
   PromisePushResources(num_resources);
   EXPECT_EQ(kMaxStreamsForTest, session_->GetNumOpenOutgoingStreams());
@@ -687,7 +704,7 @@
 // draining, a queued promised stream will become open and send push response.
 TEST_P(QuicSimpleServerSessionServerPushTest,
        HandlePromisedPushRequestsAfterStreamDraining) {
-  ConsumeHeadersStreamData();
+  MaybeConsumeHeadersStreamData();
   size_t num_resources = kMaxStreamsForTest + 1;
   QuicByteCount data_frame_header_length = PromisePushResources(num_resources);
   QuicStreamId next_out_going_stream_id =
@@ -696,6 +713,16 @@
   // After an open stream is marked draining, a new stream is expected to be
   // created and a response sent on the stream.
   QuicStreamOffset offset = 0;
+  if (VersionUsesQpack(connection_->transport_version())) {
+    EXPECT_CALL(*connection_,
+                SendStreamData(next_out_going_stream_id,
+                               kHeadersFrameHeaderLength, offset, NO_FIN));
+    offset += kHeadersFrameHeaderLength;
+    EXPECT_CALL(*connection_,
+                SendStreamData(next_out_going_stream_id,
+                               kHeadersFramePayloadLength, offset, NO_FIN));
+    offset += kHeadersFramePayloadLength;
+  }
   if (VersionHasDataFrameHeader(connection_->transport_version())) {
     EXPECT_CALL(*connection_,
                 SendStreamData(next_out_going_stream_id,
@@ -728,7 +755,7 @@
 // prevent a promised resource to be send out.
 TEST_P(QuicSimpleServerSessionServerPushTest,
        ResetPromisedStreamToCancelServerPush) {
-  ConsumeHeadersStreamData();
+  MaybeConsumeHeadersStreamData();
 
   // Having two extra resources to be send later. One of them will be reset, so
   // when opened stream become close, only one will become open.
@@ -763,6 +790,16 @@
       GetNthServerInitiatedUnidirectionalId(kMaxStreamsForTest);
   InSequence s;
   QuicStreamOffset offset = 0;
+  if (VersionUsesQpack(connection_->transport_version())) {
+    EXPECT_CALL(*connection_,
+                SendStreamData(stream_not_reset, kHeadersFrameHeaderLength,
+                               offset, NO_FIN));
+    offset += kHeadersFrameHeaderLength;
+    EXPECT_CALL(*connection_,
+                SendStreamData(stream_not_reset, kHeadersFramePayloadLength,
+                               offset, NO_FIN));
+    offset += kHeadersFramePayloadLength;
+  }
   if (VersionHasDataFrameHeader(connection_->transport_version())) {
     EXPECT_CALL(*connection_,
                 SendStreamData(stream_not_reset, data_frame_header_length,
@@ -791,7 +828,7 @@
 // the queue to be send out.
 TEST_P(QuicSimpleServerSessionServerPushTest,
        CloseStreamToHandleMorePromisedStream) {
-  ConsumeHeadersStreamData();
+  MaybeConsumeHeadersStreamData();
   size_t num_resources = kMaxStreamsForTest + 1;
   if (IsVersion99()) {
     // V99 will send out a stream-id-blocked frame when the we desired to exceed
@@ -816,6 +853,16 @@
                 OnStreamReset(stream_got_reset, QUIC_RST_ACKNOWLEDGEMENT));
   }
   QuicStreamOffset offset = 0;
+  if (VersionUsesQpack(connection_->transport_version())) {
+    EXPECT_CALL(*connection_,
+                SendStreamData(stream_to_open, kHeadersFrameHeaderLength,
+                               offset, NO_FIN));
+    offset += kHeadersFrameHeaderLength;
+    EXPECT_CALL(*connection_,
+                SendStreamData(stream_to_open, kHeadersFramePayloadLength,
+                               offset, NO_FIN));
+    offset += kHeadersFramePayloadLength;
+  }
   if (VersionHasDataFrameHeader(connection_->transport_version())) {
     EXPECT_CALL(*connection_,
                 SendStreamData(stream_to_open, data_frame_header_length, offset,