Write push promise on request streams. This CL migrates push promise from headers stream to request streams, and HTTP/3 push promise format is used in v99. gfe-relnote: v99 only, not used in prod. PiperOrigin-RevId: 260185991 Change-Id: I676cb4ebb71f72b321934c91d77ce4911f1d7225
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc index 0f6a7b1..cbfb84d 100644 --- a/quic/core/http/end_to_end_test.cc +++ b/quic/core/http/end_to_end_test.cc
@@ -3067,12 +3067,19 @@ QUIC_DVLOG(1) << "send request for /push_example"; EXPECT_EQ(kBody, client_->SendSynchronousRequest( "https://example.com/push_example")); - QuicHeadersStream* headers_stream = QuicSpdySessionPeer::GetHeadersStream( - client_->client()->client_session()); - QuicStreamSequencer* sequencer = QuicStreamPeer::sequencer(headers_stream); - // Headers stream's sequencer buffer shouldn't be released because server push - // hasn't finished yet. - EXPECT_TRUE(QuicStreamSequencerPeer::IsUnderlyingBufferAllocated(sequencer)); + QuicStreamSequencer* sequencer; + if (!VersionUsesQpack(client_->client() + ->client_session() + ->connection() + ->transport_version())) { + QuicHeadersStream* headers_stream = QuicSpdySessionPeer::GetHeadersStream( + client_->client()->client_session()); + sequencer = QuicStreamPeer::sequencer(headers_stream); + // Headers stream's sequencer buffer shouldn't be released because server + // push hasn't finished yet. + EXPECT_TRUE( + QuicStreamSequencerPeer::IsUnderlyingBufferAllocated(sequencer)); + } for (const std::string& url : push_urls) { QUIC_DVLOG(1) << "send request for pushed stream on url " << url; @@ -3082,7 +3089,13 @@ QUIC_DVLOG(1) << "response body " << response_body; EXPECT_EQ(expected_body, response_body); } - EXPECT_FALSE(QuicStreamSequencerPeer::IsUnderlyingBufferAllocated(sequencer)); + if (!VersionUsesQpack(client_->client() + ->client_session() + ->connection() + ->transport_version())) { + EXPECT_FALSE( + QuicStreamSequencerPeer::IsUnderlyingBufferAllocated(sequencer)); + } } TEST_P(EndToEndTestServerPush, ServerPushUnderLimit) {
diff --git a/quic/core/http/http_decoder.cc b/quic/core/http/http_decoder.cc index f8de083..d424c04 100644 --- a/quic/core/http/http_decoder.cc +++ b/quic/core/http/http_decoder.cc
@@ -219,6 +219,7 @@ if (current_frame_length_ == remaining_frame_length_) { QuicByteCount bytes_remaining = reader->BytesRemaining(); PushId push_id; + QuicByteCount push_id_length = reader->PeekVarInt62Length(); // TODO(rch): Handle partial delivery of this field. if (!reader->ReadVarInt62(&push_id)) { RaiseError(QUIC_INTERNAL_ERROR, "Unable to read push_id"); @@ -226,9 +227,11 @@ } remaining_frame_length_ -= bytes_remaining - reader->BytesRemaining(); if (!visitor_->OnPushPromiseFrameStart( - push_id, Http3FrameLengths(current_length_field_length_ + - current_type_field_length_, - current_frame_length_))) { + push_id, + Http3FrameLengths( + current_length_field_length_ + current_type_field_length_, + current_frame_length_), + push_id_length)) { continue_processing = false; break; }
diff --git a/quic/core/http/http_decoder.h b/quic/core/http/http_decoder.h index 80ba385..723251b 100644 --- a/quic/core/http/http_decoder.h +++ b/quic/core/http/http_decoder.h
@@ -101,7 +101,8 @@ // Called when a PUSH_PROMISE frame has been received for |push_id|. virtual bool OnPushPromiseFrameStart(PushId push_id, - Http3FrameLengths frame_length) = 0; + Http3FrameLengths frame_length, + QuicByteCount push_id_length) = 0; // Called when part of the payload of a PUSH_PROMISE frame has been read. // May be called multiple times for a single frame. |payload| is guaranteed // to be non-empty.
diff --git a/quic/core/http/http_decoder_test.cc b/quic/core/http/http_decoder_test.cc index 635268b..be47ee4 100644 --- a/quic/core/http/http_decoder_test.cc +++ b/quic/core/http/http_decoder_test.cc
@@ -51,8 +51,10 @@ MOCK_METHOD1(OnHeadersFramePayload, bool(QuicStringPiece payload)); MOCK_METHOD0(OnHeadersFrameEnd, bool()); - MOCK_METHOD2(OnPushPromiseFrameStart, - bool(PushId push_id, Http3FrameLengths frame_lengths)); + MOCK_METHOD3(OnPushPromiseFrameStart, + bool(PushId push_id, + Http3FrameLengths frame_lengths, + QuicByteCount push_id_length)); MOCK_METHOD1(OnPushPromiseFramePayload, bool(QuicStringPiece payload)); MOCK_METHOD0(OnPushPromiseFrameEnd, bool()); @@ -78,7 +80,7 @@ ON_CALL(visitor_, OnHeadersFrameStart(_)).WillByDefault(Return(true)); ON_CALL(visitor_, OnHeadersFramePayload(_)).WillByDefault(Return(true)); ON_CALL(visitor_, OnHeadersFrameEnd()).WillByDefault(Return(true)); - ON_CALL(visitor_, OnPushPromiseFrameStart(_, _)) + ON_CALL(visitor_, OnPushPromiseFrameStart(_, _, _)) .WillByDefault(Return(true)); ON_CALL(visitor_, OnPushPromiseFramePayload(_)).WillByDefault(Return(true)); ON_CALL(visitor_, OnPushPromiseFrameEnd()).WillByDefault(Return(true)); @@ -209,7 +211,7 @@ "Headers"; // Header Block // Visitor pauses processing. - EXPECT_CALL(visitor_, OnPushPromiseFrameStart(1, Http3FrameLengths(2, 8))) + EXPECT_CALL(visitor_, OnPushPromiseFrameStart(1, Http3FrameLengths(2, 8), 1)) .WillOnce(Return(false)); QuicStringPiece remaining_input(input); QuicByteCount processed_bytes = @@ -228,7 +230,7 @@ EXPECT_EQ("", decoder_.error_detail()); // Process the full frame. - EXPECT_CALL(visitor_, OnPushPromiseFrameStart(1, Http3FrameLengths(2, 8))); + EXPECT_CALL(visitor_, OnPushPromiseFrameStart(1, Http3FrameLengths(2, 8), 1)); EXPECT_CALL(visitor_, OnPushPromiseFramePayload(QuicStringPiece("Headers"))); EXPECT_CALL(visitor_, OnPushPromiseFrameEnd()); EXPECT_EQ(input.size(), ProcessInput(input)); @@ -236,7 +238,7 @@ EXPECT_EQ("", decoder_.error_detail()); // Process the frame incrementally. - EXPECT_CALL(visitor_, OnPushPromiseFrameStart(1, Http3FrameLengths(2, 8))); + EXPECT_CALL(visitor_, OnPushPromiseFrameStart(1, Http3FrameLengths(2, 8), 1)); EXPECT_CALL(visitor_, OnPushPromiseFramePayload(QuicStringPiece("H"))); EXPECT_CALL(visitor_, OnPushPromiseFramePayload(QuicStringPiece("e"))); EXPECT_CALL(visitor_, OnPushPromiseFramePayload(QuicStringPiece("a"))); @@ -717,7 +719,7 @@ "\x01"; // Push Id // Visitor pauses processing. - EXPECT_CALL(visitor_, OnPushPromiseFrameStart(1, Http3FrameLengths(2, 1))) + EXPECT_CALL(visitor_, OnPushPromiseFrameStart(1, Http3FrameLengths(2, 1), 1)) .WillOnce(Return(false)); EXPECT_EQ(input.size(), ProcessInputWithGarbageAppended(input)); @@ -727,14 +729,14 @@ EXPECT_EQ("", decoder_.error_detail()); // Process the full frame. - EXPECT_CALL(visitor_, OnPushPromiseFrameStart(1, Http3FrameLengths(2, 1))); + EXPECT_CALL(visitor_, OnPushPromiseFrameStart(1, Http3FrameLengths(2, 1), 1)); EXPECT_CALL(visitor_, OnPushPromiseFrameEnd()); EXPECT_EQ(input.size(), ProcessInput(input)); EXPECT_EQ(QUIC_NO_ERROR, decoder_.error()); EXPECT_EQ("", decoder_.error_detail()); // Process the frame incrementally. - EXPECT_CALL(visitor_, OnPushPromiseFrameStart(1, Http3FrameLengths(2, 1))); + EXPECT_CALL(visitor_, OnPushPromiseFrameStart(1, Http3FrameLengths(2, 1), 1)); EXPECT_CALL(visitor_, OnPushPromiseFrameEnd()); ProcessInputCharByChar(input); EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
diff --git a/quic/core/http/quic_headers_stream_test.cc b/quic/core/http/quic_headers_stream_test.cc index 89338a8..e3e03dd 100644 --- a/quic/core/http/quic_headers_stream_test.cc +++ b/quic/core/http/quic_headers_stream_test.cc
@@ -166,6 +166,9 @@ std::vector<TestParams> params; ParsedQuicVersionVector all_supported_versions = AllSupportedVersions(); for (size_t i = 0; i < all_supported_versions.size(); ++i) { + if (VersionUsesQpack(all_supported_versions[i].transport_version)) { + continue; + } for (Perspective p : {Perspective::IS_SERVER, Perspective::IS_CLIENT}) { params.emplace_back(all_supported_versions[i], p); } @@ -378,10 +381,6 @@ } 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}) { @@ -398,9 +397,6 @@ } TEST_P(QuicHeadersStreamTest, WritePushPromises) { - if (GetParam().version.DoesNotHaveHeadersStream()) { - return; - } for (QuicStreamId stream_id = client_id_1_; stream_id < client_id_3_; stream_id += next_stream_id_) { QuicStreamId promised_stream_id = NextPromisedStreamId(); @@ -436,10 +432,6 @@ } 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}) { @@ -473,9 +465,6 @@ } TEST_P(QuicHeadersStreamTest, ProcessPushPromise) { - if (GetParam().version.DoesNotHaveHeadersStream()) { - return; - } if (perspective() == Perspective::IS_SERVER) { return; } @@ -514,9 +503,6 @@ } TEST_P(QuicHeadersStreamTest, ProcessPriorityFrame) { - if (GetParam().version.DoesNotHaveHeadersStream()) { - return; - } QuicStreamId parent_stream_id = 0; for (SpdyPriority priority = 0; priority < 7; ++priority) { for (QuicStreamId stream_id = client_id_1_; stream_id < client_id_3_; @@ -571,10 +557,6 @@ } TEST_P(QuicHeadersStreamTest, ProcessLargeRawData) { - if (VersionUsesQpack(transport_version())) { - return; - } - // 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 // buffer size, which is 32K. @@ -749,10 +731,6 @@ } TEST_P(QuicHeadersStreamTest, HpackDecoderDebugVisitor) { - if (VersionUsesQpack(transport_version())) { - return; - } - auto hpack_decoder_visitor = QuicMakeUnique<StrictMock<MockQuicHpackDebugVisitor>>(); { @@ -806,10 +784,6 @@ } 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_receive_control_stream.cc b/quic/core/http/quic_receive_control_stream.cc index 922dc8c..d870d21 100644 --- a/quic/core/http/quic_receive_control_stream.cc +++ b/quic/core/http/quic_receive_control_stream.cc
@@ -106,7 +106,8 @@ } bool OnPushPromiseFrameStart(PushId /*push_id*/, - Http3FrameLengths /*frame_length*/) override { + Http3FrameLengths /*frame_length*/, + QuicByteCount /*push_id_length*/) override { CloseConnectionOnWrongFrame("Push Promise"); return false; }
diff --git a/quic/core/http/quic_spdy_session.cc b/quic/core/http/quic_spdy_session.cc index 50332ee..b57ad72 100644 --- a/quic/core/http/quic_spdy_session.cc +++ b/quic/core/http/quic_spdy_session.cc
@@ -520,15 +520,27 @@ return; } - SpdyPushPromiseIR push_promise(original_stream_id, promised_stream_id, - std::move(headers)); - // PUSH_PROMISE must not be the last frame sent out, at least followed by - // response headers. - push_promise.set_fin(false); + if (!VersionHasStreamType(connection()->transport_version())) { + SpdyPushPromiseIR push_promise(original_stream_id, promised_stream_id, + std::move(headers)); + // PUSH_PROMISE must not be the last frame sent out, at least followed by + // response headers. + push_promise.set_fin(false); - SpdySerializedFrame frame(spdy_framer_.SerializeFrame(push_promise)); - headers_stream()->WriteOrBufferData( - QuicStringPiece(frame.data(), frame.size()), false, nullptr); + SpdySerializedFrame frame(spdy_framer_.SerializeFrame(push_promise)); + headers_stream()->WriteOrBufferData( + QuicStringPiece(frame.data(), frame.size()), false, nullptr); + return; + } + + // Encode header list. + std::string encoded_headers = + qpack_encoder_->EncodeHeaderList(original_stream_id, &headers); + PushPromiseFrame frame; + frame.push_id = promised_stream_id; + frame.headers = encoded_headers; + QuicSpdyStream* stream = GetSpdyDataStream(original_stream_id); + stream->WritePushPromise(frame); } void QuicSpdySession::SendMaxHeaderListSize(size_t value) { @@ -693,6 +705,11 @@ void QuicSpdySession::OnHeaderList(const QuicHeaderList& header_list) { QUIC_DVLOG(1) << "Received header list for stream " << stream_id_ << ": " << header_list.DebugString(); + // This code path is only executed for push promise in IETF QUIC. + if (VersionUsesQpack(connection()->transport_version())) { + DCHECK(promised_stream_id_ != + QuicUtils::GetInvalidStreamId(connection()->transport_version())); + } if (promised_stream_id_ == QuicUtils::GetInvalidStreamId(connection()->transport_version())) { OnStreamHeaderList(stream_id_, fin_, frame_len_, header_list);
diff --git a/quic/core/http/quic_spdy_session.h b/quic/core/http/quic_spdy_session.h index 1ce8166..8d8bbda 100644 --- a/quic/core/http/quic_spdy_session.h +++ b/quic/core/http/quic_spdy_session.h
@@ -175,6 +175,18 @@ // Returns true if the session has active request streams. bool HasActiveRequestStreams() const; + // Called when the size of the compressed frame payload is available. + void OnCompressedFrameSize(size_t frame_len); + + // Called when a PUSH_PROMISE frame has been received. + void OnPushPromise(spdy::SpdyStreamId stream_id, + spdy::SpdyStreamId promised_stream_id); + + // Called when the complete list of headers is available. + void OnHeaderList(const QuicHeaderList& header_list); + + QuicStreamId promised_stream_id() const { return promised_stream_id_; } + // Initialze HTTP/3 unidirectional streams if |unidirectional| is true and // those streams are not initialized yet. void OnCanCreateNewOutgoingStream(bool unidirectional) override; @@ -251,20 +263,10 @@ const spdy::SpdyStreamPrecedence& precedence, bool fin); - // Called when a PUSH_PROMISE frame has been received. - void OnPushPromise(spdy::SpdyStreamId stream_id, - spdy::SpdyStreamId promised_stream_id); - // Called when a PRIORITY frame has been received. void OnPriority(spdy::SpdyStreamId stream_id, const spdy::SpdyStreamPrecedence& precedence); - // Called when the complete list of headers is available. - void OnHeaderList(const QuicHeaderList& header_list); - - // Called when the size of the compressed frame payload is available. - void OnCompressedFrameSize(size_t frame_len); - // Initializes HTTP/3 unidirectional streams if not yet initialzed. void MaybeInitializeHttp3UnidirectionalStreams();
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc index ca1345d..c9fa852 100644 --- a/quic/core/http/quic_spdy_stream.cc +++ b/quic/core/http/quic_spdy_stream.cc
@@ -124,23 +124,32 @@ return stream_->OnHeadersFrameEnd(); } - bool OnPushPromiseFrameStart(PushId /*push_id*/, - Http3FrameLengths /*frame_length*/) override { - // TODO(b/137554973): Consume frame header. - CloseConnectionOnWrongFrame("Push Promise"); - return false; + bool OnPushPromiseFrameStart(PushId push_id, + Http3FrameLengths frame_length, + QuicByteCount push_id_length) override { + if (!VersionHasStreamType(stream_->transport_version())) { + CloseConnectionOnWrongFrame("Push Promise"); + return false; + } + return stream_->OnPushPromiseFrameStart(push_id, frame_length, + push_id_length); } bool OnPushPromiseFramePayload(QuicStringPiece payload) override { - // TODO(b/137554973): Consume frame payload. DCHECK(!payload.empty()); - CloseConnectionOnWrongFrame("Push Promise"); - return false; + if (!VersionUsesQpack(stream_->transport_version())) { + CloseConnectionOnWrongFrame("Push Promise"); + return false; + } + return stream_->OnPushPromiseFramePayload(payload); } bool OnPushPromiseFrameEnd() override { - CloseConnectionOnWrongFrame("Push Promise"); - return false; + if (!VersionUsesQpack(stream_->transport_version())) { + CloseConnectionOnWrongFrame("Push Promise"); + return false; + } + return stream_->OnPushPromiseFrameEnd(); } bool OnUnknownFrameStart(uint64_t /* frame_type */, @@ -342,6 +351,35 @@ return bytes_written; } +void QuicSpdyStream::WritePushPromise(const PushPromiseFrame& frame) { + DCHECK(VersionUsesQpack(transport_version())); + std::unique_ptr<char[]> push_promise_frame_with_id; + const size_t push_promise_frame_length = + encoder_.SerializePushPromiseFrameWithOnlyPushId( + frame, &push_promise_frame_with_id); + + unacked_frame_headers_offsets_.Add(send_buffer().stream_offset(), + send_buffer().stream_offset() + + push_promise_frame_length + + frame.headers.length()); + + // Write Push Promise frame header and push id. + QUIC_DLOG(INFO) << "Stream " << id() + << " is writing Push Promise frame header of length " + << push_promise_frame_length << " , with promised id " + << frame.push_id; + WriteOrBufferData(QuicStringPiece(push_promise_frame_with_id.get(), + push_promise_frame_length), + /* fin = */ false, /* ack_listener = */ nullptr); + + // Write response headers. + QUIC_DLOG(INFO) << "Stream " << id() + << " is writing Push Promise request header of length " + << frame.headers.length(); + WriteOrBufferData(frame.headers, /* fin = */ false, + /* ack_listener = */ nullptr); +} + QuicConsumedData QuicSpdyStream::WritevBody(const struct iovec* iov, int count, bool fin) { @@ -898,11 +936,48 @@ return !sequencer()->IsClosed() && !reading_stopped(); } +bool QuicSpdyStream::OnPushPromiseFrameStart(PushId push_id, + Http3FrameLengths frame_length, + QuicByteCount push_id_length) { + DCHECK(VersionHasStreamType(transport_version())); + DCHECK(!qpack_decoded_headers_accumulator_); + + // TODO(renjietang): Check max push id and handle errors. + spdy_session_->OnPushPromise(id(), push_id); + sequencer()->MarkConsumed( + body_buffer_.OnNonBody(frame_length.header_length + push_id_length)); + + qpack_decoded_headers_accumulator_ = + QuicMakeUnique<QpackDecodedHeadersAccumulator>( + id(), spdy_session_->qpack_decoder(), this, + spdy_session_->max_inbound_header_list_size()); + + return true; +} + +bool QuicSpdyStream::OnPushPromiseFramePayload(QuicStringPiece payload) { + spdy_session_->OnCompressedFrameSize(payload.length()); + return OnHeadersFramePayload(payload); +} + +bool QuicSpdyStream::OnPushPromiseFrameEnd() { + DCHECK(VersionUsesQpack(transport_version())); + + OnHeadersFrameEnd(); + return !sequencer()->IsClosed() && !reading_stopped(); +} + void QuicSpdyStream::ProcessDecodedHeaders(const QuicHeaderList& headers) { - const QuicByteCount frame_length = headers_decompressed_ - ? trailers_payload_length_ - : headers_payload_length_; - OnStreamHeaderList(/* fin = */ false, frame_length, headers); + if (spdy_session_->promised_stream_id() == + QuicUtils::GetInvalidStreamId( + session()->connection()->transport_version())) { + const QuicByteCount frame_length = headers_decompressed_ + ? trailers_payload_length_ + : headers_payload_length_; + OnStreamHeaderList(/* fin = */ false, frame_length, headers); + } else { + spdy_session_->OnHeaderList(headers); + } qpack_decoded_headers_accumulator_.reset(); }
diff --git a/quic/core/http/quic_spdy_stream.h b/quic/core/http/quic_spdy_stream.h index bb111a9..b2e9c84 100644 --- a/quic/core/http/quic_spdy_stream.h +++ b/quic/core/http/quic_spdy_stream.h
@@ -126,6 +126,9 @@ spdy::SpdyHeaderBlock trailer_block, QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener); + // Serializes |frame| and writes the encoded push promise data. + void WritePushPromise(const PushPromiseFrame& frame); + // Override to report newly acked bytes via ack_listener_. bool OnStreamFrameAcked(QuicStreamOffset offset, QuicByteCount data_length, @@ -255,6 +258,11 @@ bool OnHeadersFrameStart(Http3FrameLengths frame_length); bool OnHeadersFramePayload(QuicStringPiece payload); bool OnHeadersFrameEnd(); + bool OnPushPromiseFrameStart(PushId push_id, + Http3FrameLengths frame_length, + QuicByteCount push_id_length); + bool OnPushPromiseFramePayload(QuicStringPiece payload); + bool OnPushPromiseFrameEnd(); // Called internally when headers are decoded. void ProcessDecodedHeaders(const QuicHeaderList& headers);
diff --git a/quic/core/http/quic_spdy_stream_test.cc b/quic/core/http/quic_spdy_stream_test.cc index 5b2ca81..f641dce 100644 --- a/quic/core/http/quic_spdy_stream_test.cc +++ b/quic/core/http/quic_spdy_stream_test.cc
@@ -2149,25 +2149,28 @@ ElementsAre(Pair("custom-key", "custom-value"))); } -TEST_P(QuicSpdyStreamTest, PushPromiseOnDataStreamShouldClose) { +TEST_P(QuicSpdyStreamTest, PushPromiseOnDataStream) { Initialize(kShouldProcessData); if (!HasFrameHeader()) { return; } + + // QPACK encoded single header field "foo: bar". + std::string headers = QuicTextUtils::HexDecode("00002a94e703626172"); + PushPromiseFrame push_promise; push_promise.push_id = 0x01; - push_promise.headers = "Headers"; + push_promise.headers = headers; std::unique_ptr<char[]> buffer; HttpEncoder encoder; uint64_t length = encoder.SerializePushPromiseFrameWithOnlyPushId(push_promise, &buffer); - QuicStreamFrame frame(stream_->id(), false, 0, buffer.get(), length); - // TODO(lassey): Check for HTTP_WRONG_STREAM error code. - EXPECT_CALL(*connection_, CloseConnection(QUIC_HTTP_DECODER_ERROR, _, _)); - stream_->OnStreamHeadersPriority( - spdy::SpdyStreamPrecedence(kV3HighestPriority)); - ProcessHeaders(false, headers_); - stream_->ConsumeHeaderList(); + std::string data = std::string(buffer.get(), length) + headers; + QuicStreamFrame frame(stream_->id(), false, 0, data); + + EXPECT_CALL(*session_, + OnPromiseHeaderList(stream_->id(), push_promise.push_id, + headers.length(), _)); stream_->OnStreamFrame(frame); }