Cloned from CL 231494986 by 'g4 patch'.
Original change by bnc@bnc:majom-bnc-chromium-google3-headers-git5:2181:citc on 2019/01/29 16:21:02.
Send HEADERS on the request stream.
In QUIC version 99, send HEADERS (including trailers) on the request stream
instead of the headers stream, compressed with QPACK instead of HPACK.
gfe-relnote: Send HEADERS on the request stream in QUIC version 99 only. Not flag protected.
PiperOrigin-RevId: 249121660
Change-Id: I933f9433da8bfffc8c2979aea742d485639b33c5
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc
index 209bc81..0e4bb8c 100644
--- a/quic/core/http/end_to_end_test.cc
+++ b/quic/core/http/end_to_end_test.cc
@@ -13,6 +13,7 @@
#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/qpack/qpack_encoder_test_utils.h"
#include "net/third_party/quiche/src/quic/core/quic_epoll_connection_helper.h"
#include "net/third_party/quiche/src/quic/core/quic_error_codes.h"
#include "net/third_party/quiche/src/quic/core/quic_framer.h"
@@ -1326,8 +1327,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) {
@@ -1965,10 +1975,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);
@@ -1981,6 +1990,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(
@@ -2145,6 +2159,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" +
@@ -2152,7 +2189,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..0127c5b 100644
--- a/quic/core/http/quic_spdy_stream.cc
+++ b/quic/core/http/quic_spdy_stream.cc
@@ -4,12 +4,17 @@
#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"
@@ -139,8 +144,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 +156,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 +174,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 +186,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 +207,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 +236,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 +257,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 +326,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 +382,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 +404,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 +464,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 +481,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 +517,20 @@
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()) &&
+ sequencer()->IsClosed()) ||
+ (!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 +538,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 +550,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 +576,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 +593,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 +742,96 @@
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());
}
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;
+ }
}
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 7853134..fb811cf 100644
--- a/quic/core/quic_versions.h
+++ b/quic/core/quic_versions.h
@@ -340,13 +340,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,