Add a new message status for sending HTTP Datagrams before SETTINGS are received.

Previously, `QuicSpdySession::SendHttp3Datagram` returned `MESSAGE_STATUS_INTERNAL_ERROR` if it was called before a SETTINGS frame was received. In this CL, I added a new enum called `MESSAGE_STATUS_SETTINGS_NOT_RECEIVED` to distinguish this case from other internal errors. This will allow a caller to take proper measures (e.g., drop or buffer datagrams) based on the return value of the method.

In QUICHE, callers report errors or log the `MESSAGE_STATUS_INTERNAL_ERROR`, so I added the `MESSAGE_STATUS_SETTINGS_NOT_RECEIVED` case in the callers to preserve the existing behavior.

In Envoy, `HttpDatagramHandler` is the only caller of the method. This change will not alter its behavior since both message statuses will be treated the same and reset the stream.

In Chromium, `QuicChromiumClientStream::Handle::WriteConnectUdpPayload()` treats both message statuses the same, so this CL will not change its behavior. After this CL is landed, the caller no longer needs to check `stream_->SupportsH3Datagram()` and instead can treat `MESSAGE_STATUS_SETTINGS_NOT_RECEIVED` the same way it treats `MESSAGE_STATUS_BLOCKED`.

PiperOrigin-RevId: 670652644
diff --git a/quiche/quic/core/http/quic_spdy_session.cc b/quiche/quic/core/http/quic_spdy_session.cc
index d53a4b2..13869f2 100644
--- a/quiche/quic/core/http/quic_spdy_session.cc
+++ b/quiche/quic/core/http/quic_spdy_session.cc
@@ -1761,9 +1761,18 @@
 MessageStatus QuicSpdySession::SendHttp3Datagram(QuicStreamId stream_id,
                                                  absl::string_view payload) {
   if (!SupportsH3Datagram()) {
-    QUIC_BUG(send http datagram too early)
-        << "Refusing to send HTTP Datagram before SETTINGS received";
-    return MESSAGE_STATUS_INTERNAL_ERROR;
+    if (LocalHttpDatagramSupport() == HttpDatagramSupport::kNone) {
+      QUIC_BUG(http datagram disabled locally)
+          << "Cannot send HTTP Datagram when disabled locally";
+      return MESSAGE_STATUS_UNSUPPORTED;
+    } else if (!settings_received_) {
+      QUIC_DLOG(INFO)
+          << "Refusing to send HTTP Datagram before SETTINGS received";
+      return MESSAGE_STATUS_SETTINGS_NOT_RECEIVED;
+    } else {
+      QUIC_DLOG(INFO) << "Refusing to send HTTP Datagram without peer support";
+      return MESSAGE_STATUS_UNSUPPORTED;
+    }
   }
   // Stream ID is sent divided by four as per the specification.
   uint64_t stream_id_to_write = stream_id / kHttpDatagramStreamIdDivisor;
diff --git a/quiche/quic/core/http/quic_spdy_stream_test.cc b/quiche/quic/core/http/quic_spdy_stream_test.cc
index 5ede717..7deb354 100644
--- a/quiche/quic/core/http/quic_spdy_stream_test.cc
+++ b/quiche/quic/core/http/quic_spdy_stream_test.cc
@@ -3377,6 +3377,44 @@
             MESSAGE_STATUS_SUCCESS);
 }
 
+TEST_P(QuicSpdyStreamTest, SendHttpDatagramWithoutLocalSupport) {
+  if (!UsesHttp3()) {
+    return;
+  }
+  Initialize(kShouldProcessData);
+  session_->set_local_http_datagram_support(HttpDatagramSupport::kNone);
+  std::string http_datagram_payload = {1, 2, 3, 4, 5, 6};
+  EXPECT_QUIC_BUG(stream_->SendHttp3Datagram(http_datagram_payload),
+                  "Cannot send HTTP Datagram when disabled locally");
+}
+
+TEST_P(QuicSpdyStreamTest, SendHttpDatagramBeforeReceivingSettings) {
+  if (!UsesHttp3()) {
+    return;
+  }
+  Initialize(kShouldProcessData);
+  session_->set_local_http_datagram_support(HttpDatagramSupport::kRfc);
+  std::string http_datagram_payload = {1, 2, 3, 4, 5, 6};
+  EXPECT_EQ(stream_->SendHttp3Datagram(http_datagram_payload),
+            MESSAGE_STATUS_SETTINGS_NOT_RECEIVED);
+}
+
+TEST_P(QuicSpdyStreamTest, SendHttpDatagramWithoutPeerSupport) {
+  if (!UsesHttp3()) {
+    return;
+  }
+  Initialize(kShouldProcessData);
+  // Support HTTP Datagrams locally, but not by the peer.
+  session_->set_local_http_datagram_support(HttpDatagramSupport::kRfc);
+  SettingsFrame settings;
+  settings.values[SETTINGS_H3_DATAGRAM] = 0;
+  session_->OnSettingsFrame(settings);
+
+  std::string http_datagram_payload = {1, 2, 3, 4, 5, 6};
+  EXPECT_EQ(stream_->SendHttp3Datagram(http_datagram_payload),
+            MESSAGE_STATUS_UNSUPPORTED);
+}
+
 TEST_P(QuicSpdyStreamTest, GetMaxDatagramSize) {
   if (!UsesHttp3()) {
     return;
diff --git a/quiche/quic/core/quic_types.cc b/quiche/quic/core/quic_types.cc
index a180fb1..31314cb 100644
--- a/quiche/quic/core/quic_types.cc
+++ b/quiche/quic/core/quic_types.cc
@@ -265,6 +265,7 @@
     RETURN_STRING_LITERAL(MESSAGE_STATUS_UNSUPPORTED);
     RETURN_STRING_LITERAL(MESSAGE_STATUS_BLOCKED);
     RETURN_STRING_LITERAL(MESSAGE_STATUS_TOO_LARGE);
+    RETURN_STRING_LITERAL(MESSAGE_STATUS_SETTINGS_NOT_RECEIVED);
     RETURN_STRING_LITERAL(MESSAGE_STATUS_INTERNAL_ERROR);
     default:
       return absl::StrCat("Unknown(", static_cast<int>(message_status), ")");
diff --git a/quiche/quic/core/quic_types.h b/quiche/quic/core/quic_types.h
index f3c5ff2..af3380b 100644
--- a/quiche/quic/core/quic_types.h
+++ b/quiche/quic/core/quic_types.h
@@ -645,6 +645,9 @@
                            // write blocked.
   MESSAGE_STATUS_TOO_LARGE,  // Failed to send message because the message is
                              // too large to fit into a single packet.
+  MESSAGE_STATUS_SETTINGS_NOT_RECEIVED,  // Failed to send message because
+                                         // SETTINGS frame has not been received
+                                         // yet.
   MESSAGE_STATUS_INTERNAL_ERROR,  // Failed to send message because connection
                                   // reaches an invalid state.
 };
@@ -872,11 +875,11 @@
 // ParsedClientHello contains client hello information extracted from a fully
 // received client hello.
 struct QUICHE_EXPORT ParsedClientHello {
-  std::string sni;                         // QUIC crypto and TLS.
-  std::string uaid;                        // QUIC crypto only.
-  std::vector<uint16_t> supported_groups;  // TLS only.
+  std::string sni;                               // QUIC crypto and TLS.
+  std::string uaid;                              // QUIC crypto only.
+  std::vector<uint16_t> supported_groups;        // TLS only.
   std::vector<uint16_t> cert_compression_algos;  // TLS only.
-  std::vector<std::string> alpns;          // QUIC crypto and TLS.
+  std::vector<std::string> alpns;                // QUIC crypto and TLS.
   // The unvalidated retry token from the last received packet of a potentially
   // multi-packet client hello. TLS only.
   std::string retry_token;
diff --git a/quiche/quic/core/web_transport_interface.h b/quiche/quic/core/web_transport_interface.h
index f4ee35e..d3a6a12 100644
--- a/quiche/quic/core/web_transport_interface.h
+++ b/quiche/quic/core/web_transport_interface.h
@@ -39,6 +39,7 @@
     case MESSAGE_STATUS_ENCRYPTION_NOT_ESTABLISHED:
     case MESSAGE_STATUS_INTERNAL_ERROR:
     case MESSAGE_STATUS_UNSUPPORTED:
+    case MESSAGE_STATUS_SETTINGS_NOT_RECEIVED:
       return webtransport::DatagramStatus(
           webtransport::DatagramStatusCode::kInternalError,
           absl::StrCat("Internal error: ", MessageStatusToString(status)));
diff --git a/quiche/quic/qbone/qbone_session_base.cc b/quiche/quic/qbone/qbone_session_base.cc
index 31cf8e1..289794b 100644
--- a/quiche/quic/qbone/qbone_session_base.cc
+++ b/quiche/quic/qbone/qbone_session_base.cc
@@ -172,6 +172,9 @@
       case MESSAGE_STATUS_BLOCKED:
         QUIC_BUG(quic_bug_10987_5) << "MESSAGE_STATUS_BLOCKED";
         break;
+      case MESSAGE_STATUS_SETTINGS_NOT_RECEIVED:
+        QUIC_BUG(quic_bug_10987_8) << "MESSAGE_STATUS_SETTINGS_NOT_RECEIVED";
+        break;
       case MESSAGE_STATUS_INTERNAL_ERROR:
         QUIC_BUG(quic_bug_10987_6) << "MESSAGE_STATUS_INTERNAL_ERROR";
         break;