Update goaway behavior. protected by gfe2_reloadable_flag_quic_http3_goaway_new_behavior.

Updating behavior to https://github.com/quicwg/base-drafts/pull/3129.
If client receives GOAWAY with Stream ID that is not client-initiated
bidirectional stream ID, it now closes connection with application error
H3_ID_ERROR instead of transport error PROTOCOL_VIOLATION (draft-04 behavior).
If server receives GOAWAY, it ignores it instead of closing connection with
H3_FRAME_UNEXPECTED (draft-27 behavior).  Stream ID/push ID carried by GOAWAY
frame is ignored in all cases.

This updates behavior to draft-28.  While we are still supporting draft-27,
this should be okay as the new behavior is more permissive.

PiperOrigin-RevId: 323358652
Change-Id: I17919364a259a38a110284bab0023779b194094e
diff --git a/quic/core/http/quic_receive_control_stream.cc b/quic/core/http/quic_receive_control_stream.cc
index e0e51a7..82d1fe1 100644
--- a/quic/core/http/quic_receive_control_stream.cc
+++ b/quic/core/http/quic_receive_control_stream.cc
@@ -10,6 +10,8 @@
 #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_flag_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
 #include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h"
 #include "net/third_party/quiche/src/common/platform/api/quiche_text_utils.h"
 
@@ -105,7 +107,9 @@
     return false;
   }
 
-  if (spdy_session()->perspective() == Perspective::IS_SERVER) {
+  if (GetQuicReloadableFlag(quic_http3_goaway_new_behavior)) {
+    QUIC_RELOADABLE_FLAG_COUNT(quic_http3_goaway_new_behavior);
+  } else if (spdy_session()->perspective() == Perspective::IS_SERVER) {
     OnWrongFrame("Go Away");
     return false;
   }
diff --git a/quic/core/http/quic_receive_control_stream_test.cc b/quic/core/http/quic_receive_control_stream_test.cc
index 91e09f2..6e677d7 100644
--- a/quic/core/http/quic_receive_control_stream_test.cc
+++ b/quic/core/http/quic_receive_control_stream_test.cc
@@ -294,14 +294,16 @@
 
   EXPECT_CALL(debug_visitor, OnGoAwayFrameReceived(goaway));
 
-  if (perspective() == Perspective::IS_SERVER) {
+  if (!GetQuicReloadableFlag(quic_http3_goaway_new_behavior) &&
+      perspective() == Perspective::IS_SERVER) {
     EXPECT_CALL(
         *connection_,
         CloseConnection(QUIC_HTTP_FRAME_UNEXPECTED_ON_CONTROL_STREAM, _, _));
   }
 
   receive_control_stream_->OnStreamFrame(frame);
-  if (perspective() == Perspective::IS_CLIENT) {
+  if (GetQuicReloadableFlag(quic_http3_goaway_new_behavior) ||
+      perspective() == Perspective::IS_CLIENT) {
     EXPECT_TRUE(session_.http3_goaway_received());
   }
 }
diff --git a/quic/core/http/quic_spdy_session.cc b/quic/core/http/quic_spdy_session.cc
index 63b6b87..e3f6dcf 100644
--- a/quic/core/http/quic_spdy_session.cc
+++ b/quic/core/http/quic_spdy_session.cc
@@ -404,7 +404,6 @@
       server_push_enabled_(true),
       ietf_server_push_enabled_(
           GetQuicFlag(FLAGS_quic_enable_http3_server_push)),
-      http3_goaway_received_(false),
       http3_goaway_sent_(false),
       http3_max_push_id_sent_(false) {
   h2_deframer_.set_visitor(spdy_framer_visitor_.get());
@@ -657,6 +656,41 @@
 }
 
 void QuicSpdySession::OnHttp3GoAway(uint64_t id) {
+  if (GetQuicReloadableFlag(quic_http3_goaway_new_behavior)) {
+    if (last_received_http3_goaway_id_.has_value() &&
+        id > last_received_http3_goaway_id_.value()) {
+      CloseConnectionWithDetails(
+          QUIC_HTTP_GOAWAY_ID_LARGER_THAN_PREVIOUS,
+          quiche::QuicheStrCat("GOAWAY received with ID ", id,
+                               " greater than previously received ID ",
+                               last_received_http3_goaway_id_.value()));
+      return;
+    }
+    last_received_http3_goaway_id_ = id;
+
+    if (perspective() == Perspective::IS_SERVER) {
+      // TODO(b/151749109): Cancel server pushes with push ID larger than |id|.
+      return;
+    }
+
+    // QuicStreamId is uint32_t.  Casting to this narrower type is well-defined
+    // and preserves the lower 32 bits.  Both IsBidirectionalStreamId() and
+    // IsIncomingStream() give correct results, because their return value is
+    // determined by the least significant two bits.
+    QuicStreamId stream_id = static_cast<QuicStreamId>(id);
+    if (!QuicUtils::IsBidirectionalStreamId(stream_id, version()) ||
+        IsIncomingStream(stream_id)) {
+      CloseConnectionWithDetails(QUIC_HTTP_GOAWAY_INVALID_STREAM_ID,
+                                 "GOAWAY with invalid stream ID");
+      return;
+    }
+
+    // TODO(b/161252736): Cancel client requests with ID larger than |id|.
+    // If |id| is larger than numeric_limits<QuicStreamId>::max(), then use
+    // max() instead of downcast value.
+    return;
+  }
+
   DCHECK_EQ(perspective(), Perspective::IS_CLIENT);
 
   // QuicStreamId is uint32_t.  Casting to this narrower type is well-defined
@@ -671,7 +705,7 @@
         "GOAWAY's last stream id has to point to a request stream");
     return;
   }
-  http3_goaway_received_ = true;
+  last_received_http3_goaway_id_ = id;
 }
 
 bool QuicSpdySession::OnStreamsBlockedFrame(
diff --git a/quic/core/http/quic_spdy_session.h b/quic/core/http/quic_spdy_session.h
index 9342da2..6dc65f3 100644
--- a/quic/core/http/quic_spdy_session.h
+++ b/quic/core/http/quic_spdy_session.h
@@ -351,7 +351,9 @@
 
   Http3DebugVisitor* debug_visitor() { return debug_visitor_; }
 
-  bool http3_goaway_received() const { return http3_goaway_received_; }
+  bool http3_goaway_received() const {
+    return last_received_http3_goaway_id_.has_value();
+  }
 
   bool http3_goaway_sent() const { return http3_goaway_sent_; }
 
@@ -577,8 +579,9 @@
   // Server push is enabled for a client by calling SetMaxPushId().
   bool ietf_server_push_enabled_;
 
-  // If the endpoint has received HTTP/3 GOAWAY frame.
-  bool http3_goaway_received_;
+  // The identifier in the most recently received GOAWAY frame.  Unset if no
+  // GOAWAY frame has been received yet.
+  quiche::QuicheOptional<uint64_t> last_received_http3_goaway_id_;
   // If the endpoint has sent HTTP/3 GOAWAY frame.
   bool http3_goaway_sent_;
 
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index df83890..18b3951 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -1114,6 +1114,29 @@
   session_.OnGoAway(go_away);
 }
 
+TEST_P(QuicSpdySessionTestServer, Http3GoAwayLargerIdThanBefore) {
+  if (!VersionUsesHttp3(transport_version())) {
+    return;
+  }
+  if (!GetQuicReloadableFlag(quic_http3_goaway_new_behavior)) {
+    return;
+  }
+
+  EXPECT_FALSE(session_.http3_goaway_received());
+  PushId push_id1 = 0;
+  session_.OnHttp3GoAway(push_id1);
+  EXPECT_TRUE(session_.http3_goaway_received());
+
+  EXPECT_CALL(
+      *connection_,
+      CloseConnection(
+          QUIC_HTTP_GOAWAY_ID_LARGER_THAN_PREVIOUS,
+          "GOAWAY received with ID 1 greater than previously received ID 0",
+          _));
+  PushId push_id2 = 1;
+  session_.OnHttp3GoAway(push_id2);
+}
+
 // Test that server session will send a connectivity probe in response to a
 // connectivity probe on the same path.
 TEST_P(QuicSpdySessionTestServer, ServerReplyToConnecitivityProbe) {
@@ -2831,16 +2854,47 @@
   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", _));
+  if (GetQuicReloadableFlag(quic_http3_goaway_new_behavior)) {
+    EXPECT_CALL(*connection_,
+                CloseConnection(QUIC_HTTP_GOAWAY_INVALID_STREAM_ID,
+                                "GOAWAY with invalid stream ID", _));
+  } else {
+    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);
 }
 
+TEST_P(QuicSpdySessionTestClient, Http3GoAwayLargerIdThanBefore) {
+  if (!VersionUsesHttp3(transport_version())) {
+    return;
+  }
+  if (!GetQuicReloadableFlag(quic_http3_goaway_new_behavior)) {
+    return;
+  }
+
+  EXPECT_FALSE(session_.http3_goaway_received());
+  QuicStreamId stream_id1 =
+      GetNthClientInitiatedBidirectionalStreamId(transport_version(), 0);
+  session_.OnHttp3GoAway(stream_id1);
+  EXPECT_TRUE(session_.http3_goaway_received());
+
+  EXPECT_CALL(
+      *connection_,
+      CloseConnection(
+          QUIC_HTTP_GOAWAY_ID_LARGER_THAN_PREVIOUS,
+          "GOAWAY received with ID 4 greater than previously received ID 0",
+          _));
+  QuicStreamId stream_id2 =
+      GetNthClientInitiatedBidirectionalStreamId(transport_version(), 1);
+  session_.OnHttp3GoAway(stream_id2);
+}
+
 // Test that receipt of CANCEL_PUSH frame does not result in closing the
 // connection.
 // TODO(b/151841240): Handle CANCEL_PUSH frames instead of ignoring them.
diff --git a/quic/core/quic_error_codes.cc b/quic/core/quic_error_codes.cc
index 1c0984a..7065363 100644
--- a/quic/core/quic_error_codes.cc
+++ b/quic/core/quic_error_codes.cc
@@ -205,6 +205,8 @@
     RETURN_STRING_LITERAL(QUIC_HTTP_STREAM_LIMIT_TOO_LOW);
     RETURN_STRING_LITERAL(QUIC_HTTP_ZERO_RTT_RESUMPTION_SETTINGS_MISMATCH);
     RETURN_STRING_LITERAL(QUIC_HTTP_ZERO_RTT_REJECTION_SETTINGS_MISMATCH);
+    RETURN_STRING_LITERAL(QUIC_HTTP_GOAWAY_INVALID_STREAM_ID);
+    RETURN_STRING_LITERAL(QUIC_HTTP_GOAWAY_ID_LARGER_THAN_PREVIOUS);
     RETURN_STRING_LITERAL(QUIC_HPACK_INDEX_VARINT_ERROR);
     RETURN_STRING_LITERAL(QUIC_HPACK_NAME_LENGTH_VARINT_ERROR);
     RETURN_STRING_LITERAL(QUIC_HPACK_VALUE_LENGTH_VARINT_ERROR);
@@ -576,6 +578,10 @@
       return {false, static_cast<uint64_t>(QuicHttp3ErrorCode::SETTINGS_ERROR)};
     case QUIC_HTTP_ZERO_RTT_REJECTION_SETTINGS_MISMATCH:
       return {true, static_cast<uint64_t>(INTERNAL_ERROR)};
+    case QUIC_HTTP_GOAWAY_INVALID_STREAM_ID:
+      return {false, static_cast<uint64_t>(QuicHttp3ErrorCode::ID_ERROR)};
+    case QUIC_HTTP_GOAWAY_ID_LARGER_THAN_PREVIOUS:
+      return {false, static_cast<uint64_t>(QuicHttp3ErrorCode::ID_ERROR)};
     case QUIC_HPACK_INDEX_VARINT_ERROR:
       return {true, static_cast<uint64_t>(INTERNAL_ERROR)};
     case QUIC_HPACK_NAME_LENGTH_VARINT_ERROR:
diff --git a/quic/core/quic_error_codes.h b/quic/core/quic_error_codes.h
index 4c13f2c..0c990c7 100644
--- a/quic/core/quic_error_codes.h
+++ b/quic/core/quic_error_codes.h
@@ -437,6 +437,11 @@
   // Received mismatched SETTINGS frame from HTTP/3 connection where early data
   // is rejected. Our implementation currently doesn't support it.
   QUIC_HTTP_ZERO_RTT_REJECTION_SETTINGS_MISMATCH = 165,
+  // Client received GOAWAY frame with stream ID that is not for a
+  // client-initiated bidirectional stream.
+  QUIC_HTTP_GOAWAY_INVALID_STREAM_ID = 166,
+  // Received GOAWAY frame with ID that is greater than previously received ID.
+  QUIC_HTTP_GOAWAY_ID_LARGER_THAN_PREVIOUS = 167,
 
   // HPACK header block decoding errors.
   // Index varint beyond implementation limit.
@@ -485,7 +490,7 @@
   QUIC_ZERO_RTT_RESUMPTION_LIMIT_REDUCED = 163,
 
   // No error. Used as bound while iterating.
-  QUIC_LAST_ERROR = 166,
+  QUIC_LAST_ERROR = 168,
 };
 // QuicErrorCodes is encoded as four octets on-the-wire when doing Google QUIC,
 // or a varint62 when doing IETF QUIC. Ensure that its value does not exceed