Support GOAWAY in HTTP/3.

Currently the GOAWAY is used and sent in the same way as gQUIC GOAWAY does, which means the content of GOAWAY frame is not used. I will follow up with more CLs to implement the real IETF usage where stream larger than the goaway id is not allowed.

gfe-relnote: protected by disabled v99 flag.
PiperOrigin-RevId: 275339124
Change-Id: I082f579f501b534cce7ef2b83c94dee5a2091e85
diff --git a/quic/core/http/quic_receive_control_stream.cc b/quic/core/http/quic_receive_control_stream.cc
index 4d78106..c949fa7 100644
--- a/quic/core/http/quic_receive_control_stream.cc
+++ b/quic/core/http/quic_receive_control_stream.cc
@@ -9,6 +9,7 @@
 #include "net/third_party/quiche/src/quic/core/http/http_constants.h"
 #include "net/third_party/quiche/src/quic/core/http/http_decoder.h"
 #include "net/third_party/quiche/src/quic/core/http/quic_spdy_session.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
 
 namespace quic {
@@ -65,9 +66,15 @@
     return false;
   }
 
-  bool OnGoAwayFrame(const GoAwayFrame& /*frame*/) override {
-    CloseConnectionOnWrongFrame("Goaway");
-    return false;
+  bool OnGoAwayFrame(const GoAwayFrame& frame) override {
+    QuicSpdySession* spdy_session =
+        static_cast<QuicSpdySession*>(stream_->session());
+    if (spdy_session->perspective() == Perspective::IS_SERVER) {
+      CloseConnectionOnWrongFrame("Go Away");
+      return false;
+    }
+    spdy_session->OnHttp3GoAway(frame.stream_id);
+    return true;
   }
 
   bool OnSettingsFrameStart(QuicByteCount header_length) override {
diff --git a/quic/core/http/quic_receive_control_stream_test.cc b/quic/core/http/quic_receive_control_stream_test.cc
index 73b30b3..c8df8a0 100644
--- a/quic/core/http/quic_receive_control_stream_test.cc
+++ b/quic/core/http/quic_receive_control_stream_test.cc
@@ -5,6 +5,7 @@
 #include "net/third_party/quiche/src/quic/core/http/quic_receive_control_stream.h"
 
 #include "net/third_party/quiche/src/quic/core/http/http_constants.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
 #include "net/third_party/quiche/src/quic/core/quic_utils.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
@@ -214,11 +215,12 @@
 }
 
 TEST_P(QuicReceiveControlStreamTest, ReceiveWrongFrame) {
-  GoAwayFrame goaway;
-  goaway.stream_id = 0x1;
+  DuplicatePushFrame dup;
+  dup.push_id = 0x1;
   HttpEncoder encoder;
   std::unique_ptr<char[]> buffer;
-  QuicByteCount header_length = encoder.SerializeGoAwayFrame(goaway, &buffer);
+  QuicByteCount header_length =
+      encoder.SerializeDuplicatePushFrame(dup, &buffer);
   std::string data = std::string(buffer.get(), header_length);
 
   QuicStreamFrame frame(receive_control_stream_->id(), false, 1, data);
@@ -245,6 +247,28 @@
   EXPECT_EQ(1u, stream_->precedence().spdy3_priority());
 }
 
+TEST_P(QuicReceiveControlStreamTest, ReceiveGoAwayFrame) {
+  GoAwayFrame goaway;
+  goaway.stream_id = 0x00;
+  HttpEncoder encoder;
+
+  std::unique_ptr<char[]> buffer;
+  QuicByteCount header_length = encoder.SerializeGoAwayFrame(goaway, &buffer);
+  std::string data = std::string(buffer.get(), header_length);
+
+  QuicStreamFrame frame(receive_control_stream_->id(), false, 1, data);
+  EXPECT_FALSE(session_.http3_goaway_received());
+
+  if (perspective() == Perspective::IS_SERVER) {
+    EXPECT_CALL(*connection_, CloseConnection(QUIC_HTTP_DECODER_ERROR, _, _));
+  }
+
+  receive_control_stream_->OnStreamFrame(frame);
+  if (perspective() == Perspective::IS_CLIENT) {
+    EXPECT_TRUE(session_.http3_goaway_received());
+  }
+}
+
 TEST_P(QuicReceiveControlStreamTest, PushPromiseOnControlStreamShouldClose) {
   PushPromiseFrame push_promise;
   push_promise.push_id = 0x01;
diff --git a/quic/core/http/quic_send_control_stream.cc b/quic/core/http/quic_send_control_stream.cc
index da7f79e..9891d66 100644
--- a/quic/core/http/quic_send_control_stream.cc
+++ b/quic/core/http/quic_send_control_stream.cc
@@ -3,11 +3,15 @@
 // found in the LICENSE file.
 
 #include "net/third_party/quiche/src/quic/core/http/quic_send_control_stream.h"
+#include <memory>
 
 #include "net/third_party/quiche/src/quic/core/http/http_constants.h"
 #include "net/third_party/quiche/src/quic/core/http/quic_spdy_session.h"
 #include "net/third_party/quiche/src/quic/core/quic_session.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_arraysize.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
 
 namespace quic {
 
@@ -90,4 +94,22 @@
                     /*fin = */ false, nullptr);
 }
 
+void QuicSendControlStream::SendGoAway(QuicStreamId stream_id) {
+  QuicConnection::ScopedPacketFlusher flusher(session()->connection());
+
+  MaybeSendSettingsFrame();
+  GoAwayFrame frame;
+  // If the peer hasn't created any stream yet. Use stream id 0 to indicate no
+  // request is accepted.
+  if (stream_id ==
+      QuicUtils::GetInvalidStreamId(session()->transport_version())) {
+    stream_id = 0;
+  }
+  frame.stream_id = stream_id;
+  std::unique_ptr<char[]> buffer;
+  QuicByteCount frame_length = encoder_.SerializeGoAwayFrame(frame, &buffer);
+  WriteOrBufferData(QuicStringPiece(buffer.get(), frame_length), false,
+                    nullptr);
+}
+
 }  // namespace quic
diff --git a/quic/core/http/quic_send_control_stream.h b/quic/core/http/quic_send_control_stream.h
index aa8fff1..414066d 100644
--- a/quic/core/http/quic_send_control_stream.h
+++ b/quic/core/http/quic_send_control_stream.h
@@ -7,6 +7,7 @@
 
 #include "net/third_party/quiche/src/quic/core/http/http_encoder.h"
 #include "net/third_party/quiche/src/quic/core/quic_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
 
 namespace quic {
@@ -42,6 +43,9 @@
   // Send |Priority| on this stream. It must be sent after settings.
   void WritePriority(const PriorityFrame& priority);
 
+  // Serialize a GOAWAY frame from |stream_id| and send it on this stream.
+  void SendGoAway(QuicStreamId stream_id);
+
   // The send control stream is write unidirectional, so this method should
   // never be called.
   void OnDataAvailable() override { QUIC_NOTREACHED(); }
diff --git a/quic/core/http/quic_spdy_client_session.cc b/quic/core/http/quic_spdy_client_session.cc
index c6f99ff..da3336d 100644
--- a/quic/core/http/quic_spdy_client_session.cc
+++ b/quic/core/http/quic_spdy_client_session.cc
@@ -53,6 +53,9 @@
     QUIC_DLOG(INFO) << "Encryption not active so no outgoing stream created.";
     return false;
   }
+  bool goaway_received = VersionUsesHttp3(transport_version())
+                             ? http3_goaway_received()
+                             : QuicSession::goaway_received();
   if (!GetQuicReloadableFlag(quic_use_common_stream_check) &&
       !VersionHasIetfQuicFrames(transport_version())) {
     if (GetNumOpenOutgoingStreams() >=
@@ -61,14 +64,14 @@
                       << "Already " << GetNumOpenOutgoingStreams() << " open.";
       return false;
     }
-    if (goaway_received() && respect_goaway_) {
+    if (goaway_received && respect_goaway_) {
       QUIC_DLOG(INFO) << "Failed to create a new outgoing stream. "
                       << "Already received goaway.";
       return false;
     }
     return true;
   }
-  if (goaway_received() && respect_goaway_) {
+  if (goaway_received && respect_goaway_) {
     QUIC_DLOG(INFO) << "Failed to create a new outgoing stream. "
                     << "Already received goaway.";
     return false;
@@ -132,7 +135,10 @@
     QUIC_BUG << "ShouldCreateIncomingStream called when disconnected";
     return false;
   }
-  if (goaway_received() && respect_goaway_) {
+  bool goaway_received = quic::VersionUsesHttp3(transport_version())
+                             ? http3_goaway_received()
+                             : QuicSession::goaway_received();
+  if (goaway_received && respect_goaway_) {
     QUIC_DLOG(INFO) << "Failed to create a new outgoing stream. "
                     << "Already received goaway.";
     return false;
diff --git a/quic/core/http/quic_spdy_session.cc b/quic/core/http/quic_spdy_session.cc
index 1372df0..5e4c9b6 100644
--- a/quic/core/http/quic_spdy_session.cc
+++ b/quic/core/http/quic_spdy_session.cc
@@ -11,6 +11,8 @@
 
 #include "net/third_party/quiche/src/quic/core/http/http_constants.h"
 #include "net/third_party/quiche/src/quic/core/http/quic_headers_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_error_codes.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.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/platform/api/quic_bug_tracker.h"
@@ -347,7 +349,9 @@
       spdy_framer_visitor_(new SpdyFramerVisitor(this)),
       max_allowed_push_id_(0),
       destruction_indicator_(123456789),
-      debug_visitor_(nullptr) {
+      debug_visitor_(nullptr),
+      http3_goaway_received_(false),
+      http3_goaway_sent_(false) {
   h2_deframer_.set_visitor(spdy_framer_visitor_.get());
   h2_deframer_.set_debug_visitor(spdy_framer_visitor_.get());
   spdy_framer_.set_debug_visitor(spdy_framer_visitor_.get());
@@ -531,6 +535,26 @@
   send_control_stream_->WritePriority(priority);
 }
 
+void QuicSpdySession::OnHttp3GoAway(QuicStreamId stream_id) {
+  DCHECK_EQ(perspective(), Perspective::IS_CLIENT);
+  if (!QuicUtils::IsBidirectionalStreamId(stream_id) ||
+      IsIncomingStream(stream_id)) {
+    CloseConnectionWithDetails(
+        QUIC_INVALID_STREAM_ID,
+        "GOAWAY's last stream id has to point to a request stream");
+    return;
+  }
+  http3_goaway_received_ = true;
+}
+
+void QuicSpdySession::SendHttp3GoAway() {
+  DCHECK_EQ(perspective(), Perspective::IS_SERVER);
+  DCHECK(VersionUsesHttp3(transport_version()));
+  http3_goaway_sent_ = true;
+  send_control_stream_->SendGoAway(
+      GetLargestPeerCreatedStreamId(/*unidirectional = */ false));
+}
+
 void QuicSpdySession::WritePushPromise(QuicStreamId original_stream_id,
                                        QuicStreamId promised_stream_id,
                                        SpdyHeaderBlock headers) {
diff --git a/quic/core/http/quic_spdy_session.h b/quic/core/http/quic_spdy_session.h
index 0eb3751..52bab28 100644
--- a/quic/core/http/quic_spdy_session.h
+++ b/quic/core/http/quic_spdy_session.h
@@ -153,6 +153,13 @@
   // Writes a HTTP/3 PRIORITY frame to the peer.
   void WriteH3Priority(const PriorityFrame& priority);
 
+  // Process received HTTP/3 GOAWAY frame. This method should only be called on
+  // the client side.
+  virtual void OnHttp3GoAway(QuicStreamId stream_id);
+
+  // Write the GOAWAY |frame| on control stream.
+  void SendHttp3GoAway();
+
   // Write |headers| for |promised_stream_id| on |original_stream_id| in a
   // PUSH_PROMISE frame to peer.
   virtual void WritePushPromise(QuicStreamId original_stream_id,
@@ -237,6 +244,10 @@
 
   Http3DebugVisitor* debug_visitor() { return debug_visitor_; }
 
+  bool http3_goaway_received() const { return http3_goaway_received_; }
+
+  bool http3_goaway_sent() const { return http3_goaway_sent_; }
+
   // Log header compression ratio histogram.
   // |using_qpack| is true for QPACK, false for HPACK.
   // |is_sent| is true for sent headers, false for received ones.
@@ -416,6 +427,11 @@
 
   // Not owned by the session.
   Http3DebugVisitor* debug_visitor_;
+
+  // If the endpoint has received HTTP/3 GOAWAY frame.
+  bool http3_goaway_received_;
+  // If the endpoint has sent HTTP/3 GOAWAY frame.
+  bool http3_goaway_sent_;
 };
 
 }  // namespace quic
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index 89f2d04..c4eb3c7 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -978,7 +978,7 @@
 
 TEST_P(QuicSpdySessionTestServer, SendGoAway) {
   if (VersionHasIetfQuicFrames(transport_version())) {
-    // GoAway frames are not in version 99
+    // HTTP/3 GOAWAY has different semantic and thus has its own test.
     return;
   }
   connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
@@ -1001,10 +1001,24 @@
   EXPECT_TRUE(session_.GetOrCreateStream(kTestStreamId));
 }
 
+TEST_P(QuicSpdySessionTestServer, SendHttp3GoAway) {
+  if (!VersionUsesHttp3(transport_version())) {
+    return;
+  }
+  connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+
+  session_.SendHttp3GoAway();
+  EXPECT_TRUE(session_.http3_goaway_sent());
+
+  const QuicStreamId kTestStreamId =
+      GetNthClientInitiatedBidirectionalStreamId(transport_version(), 0);
+  EXPECT_CALL(*connection_, OnStreamReset(kTestStreamId, _)).Times(0);
+  EXPECT_TRUE(session_.GetOrCreateStream(kTestStreamId));
+}
+
 TEST_P(QuicSpdySessionTestServer, DoNotSendGoAwayTwice) {
   if (VersionHasIetfQuicFrames(transport_version())) {
-    // TODO(b/118808809): Enable this test for version 99 when GOAWAY is
-    // supported.
+    // HTTP/3 GOAWAY doesn't have such restriction.
     return;
   }
   EXPECT_CALL(*connection_, SendControlFrame(_))
@@ -1016,8 +1030,7 @@
 
 TEST_P(QuicSpdySessionTestServer, InvalidGoAway) {
   if (VersionHasIetfQuicFrames(transport_version())) {
-    // TODO(b/118808809): Enable this test for version 99 when GOAWAY is
-    // supported.
+    // HTTP/3 GOAWAY has different semantics and thus has its own test.
     return;
   }
   QuicGoAwayFrame go_away(kInvalidControlFrameId, QUIC_PEER_GOING_AWAY,
@@ -2589,6 +2602,20 @@
   session_.OnStreamFrame(frame);
 }
 
+TEST_P(QuicSpdySessionTestClient, InvalidHttp3GoAway) {
+  if (!VersionUsesHttp3(transport_version())) {
+    return;
+  }
+  EXPECT_CALL(
+      *connection_,
+      CloseConnection(
+          QUIC_INVALID_STREAM_ID,
+          "GOAWAY's last stream id has to point to a request stream", _));
+  QuicStreamId stream_id =
+      GetNthServerInitiatedUnidirectionalStreamId(transport_version(), 0);
+  session_.OnHttp3GoAway(stream_id);
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc
index b6e4b83..e27cc31 100644
--- a/quic/core/quic_session.cc
+++ b/quic/core/quic_session.cc
@@ -1476,6 +1476,13 @@
       largest_peer_created_stream_id);
 }
 
+QuicStreamId QuicSession::GetLargestPeerCreatedStreamId(
+    bool unidirectional) const {
+  // This method is only used in IETF QUIC.
+  DCHECK(VersionHasIetfQuicFrames(transport_version()));
+  return v99_streamid_manager_.GetLargestPeerCreatedStreamId(unidirectional);
+}
+
 bool QuicSession::IsClosedStream(QuicStreamId id) {
   DCHECK_NE(QuicUtils::GetInvalidStreamId(transport_version()), id);
   if (IsOpenStream(id)) {
diff --git a/quic/core/quic_session.h b/quic/core/quic_session.h
index 9a2f3aa..23f1031 100644
--- a/quic/core/quic_session.h
+++ b/quic/core/quic_session.h
@@ -23,6 +23,7 @@
 #include "net/third_party/quiche/src/quic/core/quic_packets.h"
 #include "net/third_party/quiche/src/quic/core/quic_stream.h"
 #include "net/third_party/quiche/src/quic/core/quic_stream_frame_data_producer.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
 #include "net/third_party/quiche/src/quic/core/quic_write_blocked_list.h"
 #include "net/third_party/quiche/src/quic/core/session_notifier_interface.h"
 #include "net/third_party/quiche/src/quic/core/uber_quic_stream_id_manager.h"
@@ -598,6 +599,10 @@
 
   bool IsHandshakeConfirmed() const { return is_handshake_confirmed_; }
 
+  // Return the largest peer created stream id depending on directionality
+  // indicated by |unidirectional|.
+  QuicStreamId GetLargestPeerCreatedStreamId(bool unidirectional) const;
+
  private:
   friend class test::QuicSessionPeer;
 
diff --git a/quic/core/quic_session_test.cc b/quic/core/quic_session_test.cc
index 9eb4fed..20b591a 100644
--- a/quic/core/quic_session_test.cc
+++ b/quic/core/quic_session_test.cc
@@ -1309,7 +1309,7 @@
 
 TEST_P(QuicSessionTestServer, SendGoAway) {
   if (VersionHasIetfQuicFrames(transport_version())) {
-    // GoAway frames are not in version 99
+    // In IETF QUIC, GOAWAY lives up in the HTTP layer.
     return;
   }
   connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
@@ -1334,8 +1334,7 @@
 
 TEST_P(QuicSessionTestServer, DoNotSendGoAwayTwice) {
   if (VersionHasIetfQuicFrames(transport_version())) {
-    // TODO(b/118808809): Enable this test for version 99 when GOAWAY is
-    // supported.
+    // In IETF QUIC, GOAWAY lives up in the HTTP layer.
     return;
   }
   EXPECT_CALL(*connection_, SendControlFrame(_))
@@ -1347,8 +1346,7 @@
 
 TEST_P(QuicSessionTestServer, InvalidGoAway) {
   if (VersionHasIetfQuicFrames(transport_version())) {
-    // TODO(b/118808809): Enable this test for version 99 when GOAWAY is
-    // supported.
+    // In IETF QUIC, GOAWAY lives up in the HTTP layer.
     return;
   }
   QuicGoAwayFrame go_away(kInvalidControlFrameId, QUIC_PEER_GOING_AWAY,
diff --git a/quic/core/quic_stream_id_manager.h b/quic/core/quic_stream_id_manager.h
index 1ac2947..4cb9a88 100644
--- a/quic/core/quic_stream_id_manager.h
+++ b/quic/core/quic_stream_id_manager.h
@@ -153,6 +153,10 @@
     largest_peer_created_stream_id_ = largest_peer_created_stream_id;
   }
 
+  QuicStreamId largest_peer_created_stream_id() const {
+    return largest_peer_created_stream_id_;
+  }
+
   // These are the limits for outgoing and incoming streams,
   // respectively. For incoming there are two limits, what has
   // been advertised to the peer and what is actually available.
diff --git a/quic/core/uber_quic_stream_id_manager.cc b/quic/core/uber_quic_stream_id_manager.cc
index f258384..3fb5be8 100644
--- a/quic/core/uber_quic_stream_id_manager.cc
+++ b/quic/core/uber_quic_stream_id_manager.cc
@@ -135,6 +135,14 @@
       largest_peer_created_stream_id);
 }
 
+QuicStreamId UberQuicStreamIdManager::GetLargestPeerCreatedStreamId(
+    bool unidirectional) const {
+  if (unidirectional) {
+    return unidirectional_stream_id_manager_.largest_peer_created_stream_id();
+  }
+  return bidirectional_stream_id_manager_.largest_peer_created_stream_id();
+}
+
 QuicStreamId UberQuicStreamIdManager::next_outgoing_bidirectional_stream_id()
     const {
   return bidirectional_stream_id_manager_.next_outgoing_stream_id();
diff --git a/quic/core/uber_quic_stream_id_manager.h b/quic/core/uber_quic_stream_id_manager.h
index a725fdd..75fcb9d 100644
--- a/quic/core/uber_quic_stream_id_manager.h
+++ b/quic/core/uber_quic_stream_id_manager.h
@@ -73,6 +73,8 @@
   void SetLargestPeerCreatedStreamId(
       QuicStreamId largest_peer_created_stream_id);
 
+  QuicStreamId GetLargestPeerCreatedStreamId(bool unidirectional) const;
+
   QuicStreamId next_outgoing_bidirectional_stream_id() const;
   QuicStreamId next_outgoing_unidirectional_stream_id() const;
 
diff --git a/quic/test_tools/quic_test_server.cc b/quic/test_tools/quic_test_server.cc
index a893830..81b54d4 100644
--- a/quic/test_tools/quic_test_server.cc
+++ b/quic/test_tools/quic_test_server.cc
@@ -230,12 +230,20 @@
                               quic_simple_server_backend) {}
 
 void ImmediateGoAwaySession::OnStreamFrame(const QuicStreamFrame& frame) {
-  SendGoAway(QUIC_PEER_GOING_AWAY, "");
+  if (VersionUsesHttp3(transport_version())) {
+    SendHttp3GoAway();
+  } else {
+    SendGoAway(QUIC_PEER_GOING_AWAY, "");
+  }
   QuicSimpleServerSession::OnStreamFrame(frame);
 }
 
 void ImmediateGoAwaySession::OnCryptoFrame(const QuicCryptoFrame& frame) {
-  SendGoAway(QUIC_PEER_GOING_AWAY, "");
+  // In IETF QUIC, GOAWAY lives up in HTTP/3 layer. Even if it's a immediate
+  // goaway session, goaway shouldn't be sent when crypto frame is received.
+  if (!VersionUsesHttp3(transport_version())) {
+    SendGoAway(QUIC_PEER_GOING_AWAY, "");
+  }
   QuicSimpleServerSession::OnCryptoFrame(frame);
 }