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);
 }