Properly support RFC 9297

Our code has had support for draft-ietf-masque-h3-datagram-09 for a while, but now that it's been published as RFC 9297, we should have the code refer to the published names. This CL is mostly a functional no-op, with the exception of adding support for the datagram capsule type of 0x00. Since we never sent capsules, this doesn't change our sender behavior. On the receiver side, we'll still keep support for the old draft identifiers for a while.

HTTP Datagrams and capsules are not used in production on our servers, so this change is not flag-protected.

PiperOrigin-RevId: 484400359
diff --git a/quiche/quic/core/http/capsule.cc b/quiche/quic/core/http/capsule.cc
index 8f653f1..fc062b9 100644
--- a/quiche/quic/core/http/capsule.cc
+++ b/quiche/quic/core/http/capsule.cc
@@ -22,10 +22,12 @@
 
 std::string CapsuleTypeToString(CapsuleType capsule_type) {
   switch (capsule_type) {
+    case CapsuleType::DATAGRAM:
+      return "DATAGRAM";
     case CapsuleType::LEGACY_DATAGRAM:
       return "LEGACY_DATAGRAM";
-    case CapsuleType::DATAGRAM_WITHOUT_CONTEXT:
-      return "DATAGRAM_WITHOUT_CONTEXT";
+    case CapsuleType::LEGACY_DATAGRAM_WITHOUT_CONTEXT:
+      return "LEGACY_DATAGRAM_WITHOUT_CONTEXT";
     case CapsuleType::CLOSE_WEBTRANSPORT_SESSION:
       return "CLOSE_WEBTRANSPORT_SESSION";
     case CapsuleType::ADDRESS_REQUEST:
@@ -45,6 +47,12 @@
 
 Capsule::Capsule(CapsuleType capsule_type) : capsule_type_(capsule_type) {
   switch (capsule_type) {
+    case CapsuleType::DATAGRAM:
+      static_assert(std::is_standard_layout<DatagramCapsule>::value &&
+                        std::is_trivially_destructible<DatagramCapsule>::value,
+                    "All inline capsule structs must have these properties");
+      datagram_capsule_ = DatagramCapsule();
+      break;
     case CapsuleType::LEGACY_DATAGRAM:
       static_assert(
           std::is_standard_layout<LegacyDatagramCapsule>::value &&
@@ -52,13 +60,14 @@
           "All inline capsule structs must have these properties");
       legacy_datagram_capsule_ = LegacyDatagramCapsule();
       break;
-    case CapsuleType::DATAGRAM_WITHOUT_CONTEXT:
+    case CapsuleType::LEGACY_DATAGRAM_WITHOUT_CONTEXT:
       static_assert(
-          std::is_standard_layout<DatagramWithoutContextCapsule>::value &&
+          std::is_standard_layout<LegacyDatagramWithoutContextCapsule>::value &&
               std::is_trivially_destructible<
-                  DatagramWithoutContextCapsule>::value,
+                  LegacyDatagramWithoutContextCapsule>::value,
           "All inline capsule structs must have these properties");
-      datagram_without_context_capsule_ = DatagramWithoutContextCapsule();
+      legacy_datagram_without_context_capsule_ =
+          LegacyDatagramWithoutContextCapsule();
       break;
     case CapsuleType::CLOSE_WEBTRANSPORT_SESSION:
       static_assert(
@@ -86,8 +95,9 @@
 void Capsule::Free() {
   switch (capsule_type_) {
     // Inlined capsule types.
+    case CapsuleType::DATAGRAM:
     case CapsuleType::LEGACY_DATAGRAM:
-    case CapsuleType::DATAGRAM_WITHOUT_CONTEXT:
+    case CapsuleType::LEGACY_DATAGRAM_WITHOUT_CONTEXT:
     case CapsuleType::CLOSE_WEBTRANSPORT_SESSION:
       // Do nothing, these are guaranteed to be trivially destructible.
       break;
@@ -108,6 +118,13 @@
 Capsule::~Capsule() { Free(); }
 
 // static
+Capsule Capsule::Datagram(absl::string_view http_datagram_payload) {
+  Capsule capsule(CapsuleType::DATAGRAM);
+  capsule.datagram_capsule().http_datagram_payload = http_datagram_payload;
+  return capsule;
+}
+
+// static
 Capsule Capsule::LegacyDatagram(absl::string_view http_datagram_payload) {
   Capsule capsule(CapsuleType::LEGACY_DATAGRAM);
   capsule.legacy_datagram_capsule().http_datagram_payload =
@@ -116,10 +133,10 @@
 }
 
 // static
-Capsule Capsule::DatagramWithoutContext(
+Capsule Capsule::LegacyDatagramWithoutContext(
     absl::string_view http_datagram_payload) {
-  Capsule capsule(CapsuleType::DATAGRAM_WITHOUT_CONTEXT);
-  capsule.datagram_without_context_capsule().http_datagram_payload =
+  Capsule capsule(CapsuleType::LEGACY_DATAGRAM_WITHOUT_CONTEXT);
+  capsule.legacy_datagram_without_context_capsule().http_datagram_payload =
       http_datagram_payload;
   return capsule;
 }
@@ -160,12 +177,15 @@
   Free();
   capsule_type_ = other.capsule_type_;
   switch (capsule_type_) {
+    case CapsuleType::DATAGRAM:
+      datagram_capsule_ = other.datagram_capsule_;
+      break;
     case CapsuleType::LEGACY_DATAGRAM:
       legacy_datagram_capsule_ = other.legacy_datagram_capsule_;
       break;
-    case CapsuleType::DATAGRAM_WITHOUT_CONTEXT:
-      datagram_without_context_capsule_ =
-          other.datagram_without_context_capsule_;
+    case CapsuleType::LEGACY_DATAGRAM_WITHOUT_CONTEXT:
+      legacy_datagram_without_context_capsule_ =
+          other.legacy_datagram_without_context_capsule_;
       break;
     case CapsuleType::CLOSE_WEBTRANSPORT_SESSION:
       close_web_transport_session_capsule_ =
@@ -199,12 +219,16 @@
     return false;
   }
   switch (capsule_type_) {
+    case CapsuleType::DATAGRAM:
+      return datagram_capsule_.http_datagram_payload ==
+             other.datagram_capsule_.http_datagram_payload;
     case CapsuleType::LEGACY_DATAGRAM:
       return legacy_datagram_capsule_.http_datagram_payload ==
              other.legacy_datagram_capsule_.http_datagram_payload;
-    case CapsuleType::DATAGRAM_WITHOUT_CONTEXT:
-      return datagram_without_context_capsule_.http_datagram_payload ==
-             other.datagram_without_context_capsule_.http_datagram_payload;
+    case CapsuleType::LEGACY_DATAGRAM_WITHOUT_CONTEXT:
+      return legacy_datagram_without_context_capsule_.http_datagram_payload ==
+             other.legacy_datagram_without_context_capsule_
+                 .http_datagram_payload;
     case CapsuleType::CLOSE_WEBTRANSPORT_SESSION:
       return close_web_transport_session_capsule_.error_code ==
                  other.close_web_transport_session_capsule_.error_code &&
@@ -227,17 +251,22 @@
 std::string Capsule::ToString() const {
   std::string rv = CapsuleTypeToString(capsule_type_);
   switch (capsule_type_) {
+    case CapsuleType::DATAGRAM:
+      absl::StrAppend(
+          &rv, "[",
+          absl::BytesToHexString(datagram_capsule_.http_datagram_payload), "]");
+      break;
     case CapsuleType::LEGACY_DATAGRAM:
       absl::StrAppend(&rv, "[",
                       absl::BytesToHexString(
                           legacy_datagram_capsule_.http_datagram_payload),
                       "]");
       break;
-    case CapsuleType::DATAGRAM_WITHOUT_CONTEXT:
+    case CapsuleType::LEGACY_DATAGRAM_WITHOUT_CONTEXT:
       absl::StrAppend(
           &rv, "[",
           absl::BytesToHexString(
-              datagram_without_context_capsule_.http_datagram_payload),
+              legacy_datagram_without_context_capsule_.http_datagram_payload),
           "]");
       break;
     case CapsuleType::CLOSE_WEBTRANSPORT_SESSION:
@@ -297,12 +326,16 @@
       static_cast<uint64_t>(capsule.capsule_type()));
   QuicByteCount capsule_data_length;
   switch (capsule.capsule_type()) {
+    case CapsuleType::DATAGRAM:
+      capsule_data_length =
+          capsule.datagram_capsule().http_datagram_payload.length();
+      break;
     case CapsuleType::LEGACY_DATAGRAM:
       capsule_data_length =
           capsule.legacy_datagram_capsule().http_datagram_payload.length();
       break;
-    case CapsuleType::DATAGRAM_WITHOUT_CONTEXT:
-      capsule_data_length = capsule.datagram_without_context_capsule()
+    case CapsuleType::LEGACY_DATAGRAM_WITHOUT_CONTEXT:
+      capsule_data_length = capsule.legacy_datagram_without_context_capsule()
                                 .http_datagram_payload.length();
       break;
     case CapsuleType::CLOSE_WEBTRANSPORT_SESSION:
@@ -365,19 +398,29 @@
     return {};
   }
   switch (capsule.capsule_type()) {
+    case CapsuleType::DATAGRAM:
+      if (!writer.WriteStringPiece(
+              capsule.datagram_capsule().http_datagram_payload)) {
+        QUIC_BUG(datagram capsule payload write fail)
+            << "Failed to write DATAGRAM CAPSULE payload";
+        return {};
+      }
+      break;
     case CapsuleType::LEGACY_DATAGRAM:
       if (!writer.WriteStringPiece(
               capsule.legacy_datagram_capsule().http_datagram_payload)) {
-        QUIC_BUG(datagram capsule payload write fail)
+        QUIC_BUG(datagram legacy capsule payload write fail)
             << "Failed to write LEGACY_DATAGRAM CAPSULE payload";
         return {};
       }
       break;
-    case CapsuleType::DATAGRAM_WITHOUT_CONTEXT:
-      if (!writer.WriteStringPiece(capsule.datagram_without_context_capsule()
-                                       .http_datagram_payload)) {
-        QUIC_BUG(datagram capsule payload write fail)
-            << "Failed to write DATAGRAM_WITHOUT_CONTEXT CAPSULE payload";
+    case CapsuleType::LEGACY_DATAGRAM_WITHOUT_CONTEXT:
+      if (!writer.WriteStringPiece(
+              capsule.legacy_datagram_without_context_capsule()
+                  .http_datagram_payload)) {
+        QUIC_BUG(datagram legacy without context capsule payload write fail)
+            << "Failed to write LEGACY_DATAGRAM_WITHOUT_CONTEXT CAPSULE "
+               "payload";
         return {};
       }
       break;
@@ -539,12 +582,16 @@
   QuicDataReader capsule_data_reader(capsule_data);
   Capsule capsule(static_cast<CapsuleType>(capsule_type64));
   switch (capsule.capsule_type()) {
+    case CapsuleType::DATAGRAM:
+      capsule.datagram_capsule().http_datagram_payload =
+          capsule_data_reader.ReadRemainingPayload();
+      break;
     case CapsuleType::LEGACY_DATAGRAM:
       capsule.legacy_datagram_capsule().http_datagram_payload =
           capsule_data_reader.ReadRemainingPayload();
       break;
-    case CapsuleType::DATAGRAM_WITHOUT_CONTEXT:
-      capsule.datagram_without_context_capsule().http_datagram_payload =
+    case CapsuleType::LEGACY_DATAGRAM_WITHOUT_CONTEXT:
+      capsule.legacy_datagram_without_context_capsule().http_datagram_payload =
           capsule_data_reader.ReadRemainingPayload();
       break;
     case CapsuleType::CLOSE_WEBTRANSPORT_SESSION:
diff --git a/quiche/quic/core/http/capsule.h b/quiche/quic/core/http/capsule.h
index 3d83a55..c7866a8 100644
--- a/quiche/quic/core/http/capsule.h
+++ b/quiche/quic/core/http/capsule.h
@@ -22,8 +22,9 @@
 
 enum class CapsuleType : uint64_t {
   // Casing in this enum matches the IETF specifications.
+  DATAGRAM = 0x00,             // RFC 9297.
   LEGACY_DATAGRAM = 0xff37a0,  // draft-ietf-masque-h3-datagram-04.
-  DATAGRAM_WITHOUT_CONTEXT =
+  LEGACY_DATAGRAM_WITHOUT_CONTEXT =
       0xff37a5,  // draft-ietf-masque-h3-datagram-05 to -08.
   CLOSE_WEBTRANSPORT_SESSION = 0x2843,
   // draft-ietf-masque-connect-ip-03.
@@ -36,10 +37,13 @@
 QUIC_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                              const CapsuleType& capsule_type);
 
+struct QUIC_EXPORT_PRIVATE DatagramCapsule {
+  absl::string_view http_datagram_payload;
+};
 struct QUIC_EXPORT_PRIVATE LegacyDatagramCapsule {
   absl::string_view http_datagram_payload;
 };
-struct QUIC_EXPORT_PRIVATE DatagramWithoutContextCapsule {
+struct QUIC_EXPORT_PRIVATE LegacyDatagramWithoutContextCapsule {
   absl::string_view http_datagram_payload;
 };
 struct QUIC_EXPORT_PRIVATE CloseWebTransportSessionCapsule {
@@ -70,16 +74,18 @@
   bool operator==(const RouteAdvertisementCapsule& other) const;
 };
 
-// Capsule from draft-ietf-masque-h3-datagram.
+// Capsule from RFC 9297.
 // IMPORTANT NOTE: Capsule does not own any of the absl::string_view memory it
 // points to. Strings saved into a capsule must outlive the capsule object. Any
 // code that sees a capsule in a callback needs to either process it immediately
 // or perform its own deep copy.
 class QUIC_EXPORT_PRIVATE Capsule {
  public:
+  static Capsule Datagram(
+      absl::string_view http_datagram_payload = absl::string_view());
   static Capsule LegacyDatagram(
       absl::string_view http_datagram_payload = absl::string_view());
-  static Capsule DatagramWithoutContext(
+  static Capsule LegacyDatagramWithoutContext(
       absl::string_view http_datagram_payload = absl::string_view());
   static Capsule CloseWebTransportSession(
       WebTransportSessionError error_code = 0,
@@ -103,6 +109,14 @@
                                                       const Capsule& capsule);
 
   CapsuleType capsule_type() const { return capsule_type_; }
+  DatagramCapsule& datagram_capsule() {
+    QUICHE_DCHECK_EQ(capsule_type_, CapsuleType::DATAGRAM);
+    return datagram_capsule_;
+  }
+  const DatagramCapsule& datagram_capsule() const {
+    QUICHE_DCHECK_EQ(capsule_type_, CapsuleType::DATAGRAM);
+    return datagram_capsule_;
+  }
   LegacyDatagramCapsule& legacy_datagram_capsule() {
     QUICHE_DCHECK_EQ(capsule_type_, CapsuleType::LEGACY_DATAGRAM);
     return legacy_datagram_capsule_;
@@ -111,14 +125,17 @@
     QUICHE_DCHECK_EQ(capsule_type_, CapsuleType::LEGACY_DATAGRAM);
     return legacy_datagram_capsule_;
   }
-  DatagramWithoutContextCapsule& datagram_without_context_capsule() {
-    QUICHE_DCHECK_EQ(capsule_type_, CapsuleType::DATAGRAM_WITHOUT_CONTEXT);
-    return datagram_without_context_capsule_;
+  LegacyDatagramWithoutContextCapsule&
+  legacy_datagram_without_context_capsule() {
+    QUICHE_DCHECK_EQ(capsule_type_,
+                     CapsuleType::LEGACY_DATAGRAM_WITHOUT_CONTEXT);
+    return legacy_datagram_without_context_capsule_;
   }
-  const DatagramWithoutContextCapsule& datagram_without_context_capsule()
-      const {
-    QUICHE_DCHECK_EQ(capsule_type_, CapsuleType::DATAGRAM_WITHOUT_CONTEXT);
-    return datagram_without_context_capsule_;
+  const LegacyDatagramWithoutContextCapsule&
+  legacy_datagram_without_context_capsule() const {
+    QUICHE_DCHECK_EQ(capsule_type_,
+                     CapsuleType::LEGACY_DATAGRAM_WITHOUT_CONTEXT);
+    return legacy_datagram_without_context_capsule_;
   }
   CloseWebTransportSessionCapsule& close_web_transport_session_capsule() {
     QUICHE_DCHECK_EQ(capsule_type_, CapsuleType::CLOSE_WEBTRANSPORT_SESSION);
@@ -154,8 +171,10 @@
     return *route_advertisement_capsule_;
   }
   absl::string_view& unknown_capsule_data() {
-    QUICHE_DCHECK(capsule_type_ != CapsuleType::LEGACY_DATAGRAM &&
-                  capsule_type_ != CapsuleType::DATAGRAM_WITHOUT_CONTEXT &&
+    QUICHE_DCHECK(capsule_type_ != CapsuleType::DATAGRAM &&
+                  capsule_type_ != CapsuleType::LEGACY_DATAGRAM &&
+                  capsule_type_ !=
+                      CapsuleType::LEGACY_DATAGRAM_WITHOUT_CONTEXT &&
                   capsule_type_ != CapsuleType::CLOSE_WEBTRANSPORT_SESSION &&
                   capsule_type_ != CapsuleType::ADDRESS_REQUEST &&
                   capsule_type_ != CapsuleType::ADDRESS_ASSIGN &&
@@ -164,8 +183,10 @@
     return unknown_capsule_data_;
   }
   const absl::string_view& unknown_capsule_data() const {
-    QUICHE_DCHECK(capsule_type_ != CapsuleType::LEGACY_DATAGRAM &&
-                  capsule_type_ != CapsuleType::DATAGRAM_WITHOUT_CONTEXT &&
+    QUICHE_DCHECK(capsule_type_ != CapsuleType::DATAGRAM &&
+                  capsule_type_ != CapsuleType::LEGACY_DATAGRAM &&
+                  capsule_type_ !=
+                      CapsuleType::LEGACY_DATAGRAM_WITHOUT_CONTEXT &&
                   capsule_type_ != CapsuleType::CLOSE_WEBTRANSPORT_SESSION &&
                   capsule_type_ != CapsuleType::ADDRESS_REQUEST &&
                   capsule_type_ != CapsuleType::ADDRESS_ASSIGN &&
@@ -178,8 +199,10 @@
   void Free();
   CapsuleType capsule_type_;
   union {
+    DatagramCapsule datagram_capsule_;
     LegacyDatagramCapsule legacy_datagram_capsule_;
-    DatagramWithoutContextCapsule datagram_without_context_capsule_;
+    LegacyDatagramWithoutContextCapsule
+        legacy_datagram_without_context_capsule_;
     CloseWebTransportSessionCapsule close_web_transport_session_capsule_;
     AddressRequestCapsule* address_request_capsule_;
     AddressAssignCapsule* address_assign_capsule_;
diff --git a/quiche/quic/core/http/capsule_test.cc b/quiche/quic/core/http/capsule_test.cc
index a686095..95869c6 100644
--- a/quiche/quic/core/http/capsule_test.cc
+++ b/quiche/quic/core/http/capsule_test.cc
@@ -68,6 +68,22 @@
   CapsuleParser capsule_parser_;
 };
 
+TEST_F(CapsuleTest, DatagramCapsule) {
+  std::string capsule_fragment = absl::HexStringToBytes(
+      "00"                // DATAGRAM capsule type
+      "08"                // capsule length
+      "a1a2a3a4a5a6a7a8"  // HTTP Datagram payload
+  );
+  std::string datagram_payload = absl::HexStringToBytes("a1a2a3a4a5a6a7a8");
+  Capsule expected_capsule = Capsule::Datagram(datagram_payload);
+  {
+    EXPECT_CALL(visitor_, OnCapsule(expected_capsule));
+    ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment));
+  }
+  ValidateParserIsEmpty();
+  TestSerialization(expected_capsule, capsule_fragment);
+}
+
 TEST_F(CapsuleTest, LegacyDatagramCapsule) {
   std::string capsule_fragment = absl::HexStringToBytes(
       "80ff37a0"          // LEGACY_DATAGRAM capsule type
@@ -84,14 +100,15 @@
   TestSerialization(expected_capsule, capsule_fragment);
 }
 
-TEST_F(CapsuleTest, DatagramWithoutContextCapsule) {
+TEST_F(CapsuleTest, LegacyDatagramWithoutContextCapsule) {
   std::string capsule_fragment = absl::HexStringToBytes(
-      "80ff37a5"          // DATAGRAM_WITHOUT_CONTEXT capsule type
+      "80ff37a5"          // LEGACY_DATAGRAM_WITHOUT_CONTEXT capsule type
       "08"                // capsule length
       "a1a2a3a4a5a6a7a8"  // HTTP Datagram payload
   );
   std::string datagram_payload = absl::HexStringToBytes("a1a2a3a4a5a6a7a8");
-  Capsule expected_capsule = Capsule::DatagramWithoutContext(datagram_payload);
+  Capsule expected_capsule =
+      Capsule::LegacyDatagramWithoutContext(datagram_payload);
   {
     EXPECT_CALL(visitor_, OnCapsule(expected_capsule));
     ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment));
@@ -236,12 +253,12 @@
 
 TEST_F(CapsuleTest, UnknownCapsule) {
   std::string capsule_fragment = absl::HexStringToBytes(
-      "33"                // unknown capsule type of 0x33
+      "17"                // unknown capsule type of 0x17
       "08"                // capsule length
       "a1a2a3a4a5a6a7a8"  // unknown capsule data
   );
   std::string unknown_capsule_data = absl::HexStringToBytes("a1a2a3a4a5a6a7a8");
-  Capsule expected_capsule = Capsule::Unknown(0x33, unknown_capsule_data);
+  Capsule expected_capsule = Capsule::Unknown(0x17, unknown_capsule_data);
   {
     EXPECT_CALL(visitor_, OnCapsule(expected_capsule));
     ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment));
@@ -252,19 +269,17 @@
 
 TEST_F(CapsuleTest, TwoCapsules) {
   std::string capsule_fragment = absl::HexStringToBytes(
-      "80ff37a5"          // DATAGRAM_WITHOUT_CONTEXT capsule type
+      "00"                // DATAGRAM capsule type
       "08"                // capsule length
       "a1a2a3a4a5a6a7a8"  // HTTP Datagram payload
-      "80ff37a5"          // DATAGRAM_WITHOUT_CONTEXT capsule type
+      "00"                // DATAGRAM capsule type
       "08"                // capsule length
       "b1b2b3b4b5b6b7b8"  // HTTP Datagram payload
   );
   std::string datagram_payload1 = absl::HexStringToBytes("a1a2a3a4a5a6a7a8");
   std::string datagram_payload2 = absl::HexStringToBytes("b1b2b3b4b5b6b7b8");
-  Capsule expected_capsule1 =
-      Capsule::DatagramWithoutContext(datagram_payload1);
-  Capsule expected_capsule2 =
-      Capsule::DatagramWithoutContext(datagram_payload2);
+  Capsule expected_capsule1 = Capsule::Datagram(datagram_payload1);
+  Capsule expected_capsule2 = Capsule::Datagram(datagram_payload2);
   {
     InSequence s;
     EXPECT_CALL(visitor_, OnCapsule(expected_capsule1));
@@ -276,13 +291,13 @@
 
 TEST_F(CapsuleTest, TwoCapsulesPartialReads) {
   std::string capsule_fragment1 = absl::HexStringToBytes(
-      "80ff37a5"  // first capsule DATAGRAM_WITHOUT_CONTEXT capsule type
-      "08"        // frist capsule length
+      "00"        // first capsule DATAGRAM capsule type
+      "08"        // first capsule length
       "a1a2a3a4"  // first half of HTTP Datagram payload of first capsule
   );
   std::string capsule_fragment2 = absl::HexStringToBytes(
       "a5a6a7a8"  // second half of HTTP Datagram payload 1
-      "80ff37a5"  // second capsule DATAGRAM_WITHOUT_CONTEXT capsule type
+      "00"        // second capsule DATAGRAM capsule type
   );
   std::string capsule_fragment3 = absl::HexStringToBytes(
       "08"                // second capsule length
@@ -291,10 +306,8 @@
   capsule_parser_.ErrorIfThereIsRemainingBufferedData();
   std::string datagram_payload1 = absl::HexStringToBytes("a1a2a3a4a5a6a7a8");
   std::string datagram_payload2 = absl::HexStringToBytes("b1b2b3b4b5b6b7b8");
-  Capsule expected_capsule1 =
-      Capsule::DatagramWithoutContext(datagram_payload1);
-  Capsule expected_capsule2 =
-      Capsule::DatagramWithoutContext(datagram_payload2);
+  Capsule expected_capsule1 = Capsule::Datagram(datagram_payload1);
+  Capsule expected_capsule2 = Capsule::Datagram(datagram_payload2);
   {
     InSequence s;
     EXPECT_CALL(visitor_, OnCapsule(expected_capsule1));
@@ -308,19 +321,17 @@
 
 TEST_F(CapsuleTest, TwoCapsulesOneByteAtATime) {
   std::string capsule_fragment = absl::HexStringToBytes(
-      "80ff37a5"          // DATAGRAM_WITHOUT_CONTEXT capsule type
+      "00"                // DATAGRAM capsule type
       "08"                // capsule length
       "a1a2a3a4a5a6a7a8"  // HTTP Datagram payload
-      "80ff37a5"          // DATAGRAM_WITHOUT_CONTEXT capsule type
+      "00"                // DATAGRAM capsule type
       "08"                // capsule length
       "b1b2b3b4b5b6b7b8"  // HTTP Datagram payload
   );
   std::string datagram_payload1 = absl::HexStringToBytes("a1a2a3a4a5a6a7a8");
   std::string datagram_payload2 = absl::HexStringToBytes("b1b2b3b4b5b6b7b8");
-  Capsule expected_capsule1 =
-      Capsule::DatagramWithoutContext(datagram_payload1);
-  Capsule expected_capsule2 =
-      Capsule::DatagramWithoutContext(datagram_payload2);
+  Capsule expected_capsule1 = Capsule::Datagram(datagram_payload1);
+  Capsule expected_capsule2 = Capsule::Datagram(datagram_payload2);
   for (size_t i = 0; i < capsule_fragment.size(); i++) {
     if (i < capsule_fragment.size() / 2 - 1) {
       EXPECT_CALL(visitor_, OnCapsule(_)).Times(0);
@@ -348,7 +359,7 @@
 
 TEST_F(CapsuleTest, PartialCapsuleThenError) {
   std::string capsule_fragment = absl::HexStringToBytes(
-      "80ff37a5"  // DATAGRAM_WITHOUT_CONTEXT capsule type
+      "00"        // DATAGRAM capsule type
       "08"        // capsule length
       "a1a2a3a4"  // first half of HTTP Datagram payload
   );
@@ -367,7 +378,7 @@
 
 TEST_F(CapsuleTest, RejectOverlyLongCapsule) {
   std::string capsule_fragment = absl::HexStringToBytes(
-                                     "33"        // unknown capsule type of 0x33
+                                     "17"        // unknown capsule type of 0x17
                                      "80123456"  // capsule length
                                      ) +
                                  std::string(1111111, '?');
diff --git a/quiche/quic/core/http/http_constants.cc b/quiche/quic/core/http/http_constants.cc
index e429678..e585535 100644
--- a/quiche/quic/core/http/http_constants.cc
+++ b/quiche/quic/core/http/http_constants.cc
@@ -18,7 +18,7 @@
     RETURN_STRING_LITERAL(SETTINGS_MAX_FIELD_SECTION_SIZE);
     RETURN_STRING_LITERAL(SETTINGS_QPACK_BLOCKED_STREAMS);
     RETURN_STRING_LITERAL(SETTINGS_H3_DATAGRAM_DRAFT04);
-    RETURN_STRING_LITERAL(SETTINGS_H3_DATAGRAM_DRAFT09);
+    RETURN_STRING_LITERAL(SETTINGS_H3_DATAGRAM);
     RETURN_STRING_LITERAL(SETTINGS_WEBTRANS_DRAFT00);
     RETURN_STRING_LITERAL(SETTINGS_ENABLE_CONNECT_PROTOCOL);
     RETURN_STRING_LITERAL(SETTINGS_ENABLE_METADATA);
diff --git a/quiche/quic/core/http/http_constants.h b/quiche/quic/core/http/http_constants.h
index b5dc19b..feec521 100644
--- a/quiche/quic/core/http/http_constants.h
+++ b/quiche/quic/core/http/http_constants.h
@@ -40,8 +40,8 @@
   SETTINGS_QPACK_BLOCKED_STREAMS = 0x07,
   // draft-ietf-masque-h3-datagram-04.
   SETTINGS_H3_DATAGRAM_DRAFT04 = 0xffd277,
-  // draft-ietf-masque-h3-datagram-09.
-  SETTINGS_H3_DATAGRAM_DRAFT09 = 0x33,
+  // RFC 9297.
+  SETTINGS_H3_DATAGRAM = 0x33,
   // draft-ietf-webtrans-http3-00
   SETTINGS_WEBTRANS_DRAFT00 = 0x2b603742,
   // draft-ietf-httpbis-h3-websockets
diff --git a/quiche/quic/core/http/quic_spdy_session.cc b/quiche/quic/core/http/quic_spdy_session.cc
index 8b6d5ea..c9a0c1e 100644
--- a/quiche/quic/core/http/quic_spdy_session.cc
+++ b/quiche/quic/core/http/quic_spdy_session.cc
@@ -530,12 +530,12 @@
       case HttpDatagramSupport::kDraft04:
         settings_.values[SETTINGS_H3_DATAGRAM_DRAFT04] = 1;
         break;
-      case HttpDatagramSupport::kDraft09:
-        settings_.values[SETTINGS_H3_DATAGRAM_DRAFT09] = 1;
+      case HttpDatagramSupport::kRfc:
+        settings_.values[SETTINGS_H3_DATAGRAM] = 1;
         break;
-      case HttpDatagramSupport::kDraft04And09:
+      case HttpDatagramSupport::kRfcAndDraft04:
+        settings_.values[SETTINGS_H3_DATAGRAM] = 1;
         settings_.values[SETTINGS_H3_DATAGRAM_DRAFT04] = 1;
-        settings_.values[SETTINGS_H3_DATAGRAM_DRAFT09] = 1;
         break;
     }
   }
@@ -1157,7 +1157,8 @@
         HttpDatagramSupport local_http_datagram_support =
             LocalHttpDatagramSupport();
         if (local_http_datagram_support != HttpDatagramSupport::kDraft04 &&
-            local_http_datagram_support != HttpDatagramSupport::kDraft04And09) {
+            local_http_datagram_support !=
+                HttpDatagramSupport::kRfcAndDraft04) {
           break;
         }
         QUIC_DVLOG(1) << ENDPOINT
@@ -1169,17 +1170,20 @@
         if (!VerifySettingIsZeroOrOne(id, value)) {
           return false;
         }
-        if (value && http_datagram_support_ != HttpDatagramSupport::kDraft09) {
-          // If both draft-04 and draft-09 are supported, use draft-09.
+        if (value && http_datagram_support_ != HttpDatagramSupport::kRfc) {
+          // If both RFC 9297 and draft-04 are supported, we use the RFC. This
+          // is implemented by ignoring SETTINGS_H3_DATAGRAM_DRAFT04 when we've
+          // already parsed SETTINGS_H3_DATAGRAM.
           http_datagram_support_ = HttpDatagramSupport::kDraft04;
         }
         break;
       }
-      case SETTINGS_H3_DATAGRAM_DRAFT09: {
+      case SETTINGS_H3_DATAGRAM: {
         HttpDatagramSupport local_http_datagram_support =
             LocalHttpDatagramSupport();
-        if (local_http_datagram_support != HttpDatagramSupport::kDraft09 &&
-            local_http_datagram_support != HttpDatagramSupport::kDraft04And09) {
+        if (local_http_datagram_support != HttpDatagramSupport::kRfc &&
+            local_http_datagram_support !=
+                HttpDatagramSupport::kRfcAndDraft04) {
           break;
         }
         QUIC_DVLOG(1) << ENDPOINT
@@ -1192,7 +1196,7 @@
           return false;
         }
         if (value) {
-          http_datagram_support_ = HttpDatagramSupport::kDraft09;
+          http_datagram_support_ = HttpDatagramSupport::kRfc;
         }
         break;
       }
@@ -1830,9 +1834,9 @@
       return "None";
     case HttpDatagramSupport::kDraft04:
       return "Draft04";
-    case HttpDatagramSupport::kDraft09:
+    case HttpDatagramSupport::kRfc:
       return "Draft09";
-    case HttpDatagramSupport::kDraft04And09:
+    case HttpDatagramSupport::kRfcAndDraft04:
       return "Draft04And09";
   }
   return absl::StrCat("Unknown(", static_cast<int>(http_datagram_support), ")");
diff --git a/quiche/quic/core/http/quic_spdy_session.h b/quiche/quic/core/http/quic_spdy_session.h
index 5c779c5..0cfb629 100644
--- a/quiche/quic/core/http/quic_spdy_session.h
+++ b/quiche/quic/core/http/quic_spdy_session.h
@@ -116,13 +116,14 @@
   virtual void OnSettingsFrameResumed(const SettingsFrame& /*frame*/) {}
 };
 
-// Whether draft-ietf-masque-h3-datagram is supported on this session and if so
-// which draft is currently in use.
+// Whether HTTP Datagrams are supported on this session and if so which version
+// is currently in use.
 enum class HttpDatagramSupport : uint8_t {
   kNone,  // HTTP Datagrams are not supported for this session.
   kDraft04,
-  kDraft09,
-  kDraft04And09,  // Only used locally for sending, we only negotiate one draft.
+  kRfc,
+  kRfcAndDraft04,  // Only used locally for sending, we only negotiate one
+                   // version.
 };
 
 QUIC_EXPORT_PRIVATE std::string HttpDatagramSupportToString(
diff --git a/quiche/quic/core/http/quic_spdy_session_test.cc b/quiche/quic/core/http/quic_spdy_session_test.cc
index fcc6103..7897570 100644
--- a/quiche/quic/core/http/quic_spdy_session_test.cc
+++ b/quiche/quic/core/http/quic_spdy_session_test.cc
@@ -3370,12 +3370,12 @@
     case HttpDatagramSupport::kDraft04:
       settings.values[SETTINGS_H3_DATAGRAM_DRAFT04] = 1;
       break;
-    case HttpDatagramSupport::kDraft09:
-      settings.values[SETTINGS_H3_DATAGRAM_DRAFT09] = 1;
+    case HttpDatagramSupport::kRfc:
+      settings.values[SETTINGS_H3_DATAGRAM] = 1;
       break;
-    case HttpDatagramSupport::kDraft04And09:
+    case HttpDatagramSupport::kRfcAndDraft04:
+      settings.values[SETTINGS_H3_DATAGRAM] = 1;
       settings.values[SETTINGS_H3_DATAGRAM_DRAFT04] = 1;
-      settings.values[SETTINGS_H3_DATAGRAM_DRAFT09] = 1;
       break;
   }
   std::string data = std::string(1, kControlStream) +
@@ -3403,7 +3403,7 @@
 TEST_P(QuicSpdySessionTestClient, HttpDatagramSettingLocal04Remote09) {
   TestHttpDatagramSetting(
       /*local_support=*/HttpDatagramSupport::kDraft04,
-      /*remote_support=*/HttpDatagramSupport::kDraft09,
+      /*remote_support=*/HttpDatagramSupport::kRfc,
       /*expected_support=*/HttpDatagramSupport::kNone,
       /*expected_datagram_supported=*/false);
 }
@@ -3411,14 +3411,14 @@
 TEST_P(QuicSpdySessionTestClient, HttpDatagramSettingLocal04Remote04And09) {
   TestHttpDatagramSetting(
       /*local_support=*/HttpDatagramSupport::kDraft04,
-      /*remote_support=*/HttpDatagramSupport::kDraft04And09,
+      /*remote_support=*/HttpDatagramSupport::kRfcAndDraft04,
       /*expected_support=*/HttpDatagramSupport::kDraft04,
       /*expected_datagram_supported=*/true);
 }
 
 TEST_P(QuicSpdySessionTestClient, HttpDatagramSettingLocal09Remote04) {
   TestHttpDatagramSetting(
-      /*local_support=*/HttpDatagramSupport::kDraft09,
+      /*local_support=*/HttpDatagramSupport::kRfc,
       /*remote_support=*/HttpDatagramSupport::kDraft04,
       /*expected_support=*/HttpDatagramSupport::kNone,
       /*expected_datagram_supported=*/false);
@@ -3426,23 +3426,23 @@
 
 TEST_P(QuicSpdySessionTestClient, HttpDatagramSettingLocal09Remote09) {
   TestHttpDatagramSetting(
-      /*local_support=*/HttpDatagramSupport::kDraft09,
-      /*remote_support=*/HttpDatagramSupport::kDraft09,
-      /*expected_support=*/HttpDatagramSupport::kDraft09,
+      /*local_support=*/HttpDatagramSupport::kRfc,
+      /*remote_support=*/HttpDatagramSupport::kRfc,
+      /*expected_support=*/HttpDatagramSupport::kRfc,
       /*expected_datagram_supported=*/true);
 }
 
 TEST_P(QuicSpdySessionTestClient, HttpDatagramSettingLocal09Remote04And09) {
   TestHttpDatagramSetting(
-      /*local_support=*/HttpDatagramSupport::kDraft09,
-      /*remote_support=*/HttpDatagramSupport::kDraft04And09,
-      /*expected_support=*/HttpDatagramSupport::kDraft09,
+      /*local_support=*/HttpDatagramSupport::kRfc,
+      /*remote_support=*/HttpDatagramSupport::kRfcAndDraft04,
+      /*expected_support=*/HttpDatagramSupport::kRfc,
       /*expected_datagram_supported=*/true);
 }
 
 TEST_P(QuicSpdySessionTestClient, HttpDatagramSettingLocal04And09Remote04) {
   TestHttpDatagramSetting(
-      /*local_support=*/HttpDatagramSupport::kDraft04And09,
+      /*local_support=*/HttpDatagramSupport::kRfcAndDraft04,
       /*remote_support=*/HttpDatagramSupport::kDraft04,
       /*expected_support=*/HttpDatagramSupport::kDraft04,
       /*expected_datagram_supported=*/true);
@@ -3450,18 +3450,18 @@
 
 TEST_P(QuicSpdySessionTestClient, HttpDatagramSettingLocal04And09Remote09) {
   TestHttpDatagramSetting(
-      /*local_support=*/HttpDatagramSupport::kDraft04And09,
-      /*remote_support=*/HttpDatagramSupport::kDraft09,
-      /*expected_support=*/HttpDatagramSupport::kDraft09,
+      /*local_support=*/HttpDatagramSupport::kRfcAndDraft04,
+      /*remote_support=*/HttpDatagramSupport::kRfc,
+      /*expected_support=*/HttpDatagramSupport::kRfc,
       /*expected_datagram_supported=*/true);
 }
 
 TEST_P(QuicSpdySessionTestClient,
        HttpDatagramSettingLocal04And09Remote04And09) {
   TestHttpDatagramSetting(
-      /*local_support=*/HttpDatagramSupport::kDraft04And09,
-      /*remote_support=*/HttpDatagramSupport::kDraft04And09,
-      /*expected_support=*/HttpDatagramSupport::kDraft09,
+      /*local_support=*/HttpDatagramSupport::kRfcAndDraft04,
+      /*remote_support=*/HttpDatagramSupport::kRfcAndDraft04,
+      /*expected_support=*/HttpDatagramSupport::kRfc,
       /*expected_datagram_supported=*/true);
 }
 TEST_P(QuicSpdySessionTestClient, WebTransportSetting) {
diff --git a/quiche/quic/core/http/quic_spdy_stream.cc b/quiche/quic/core/http/quic_spdy_stream.cc
index 2584a39..ee1e685 100644
--- a/quiche/quic/core/http/quic_spdy_stream.cc
+++ b/quiche/quic/core/http/quic_spdy_stream.cc
@@ -1370,13 +1370,16 @@
     return false;
   }
   switch (capsule.capsule_type()) {
+    case CapsuleType::DATAGRAM: {
+      HandleReceivedDatagram(capsule.datagram_capsule().http_datagram_payload);
+    } break;
     case CapsuleType::LEGACY_DATAGRAM: {
       HandleReceivedDatagram(
           capsule.legacy_datagram_capsule().http_datagram_payload);
     } break;
-    case CapsuleType::DATAGRAM_WITHOUT_CONTEXT: {
-      HandleReceivedDatagram(
-          capsule.datagram_without_context_capsule().http_datagram_payload);
+    case CapsuleType::LEGACY_DATAGRAM_WITHOUT_CONTEXT: {
+      HandleReceivedDatagram(capsule.legacy_datagram_without_context_capsule()
+                                 .http_datagram_payload);
     } break;
     case CapsuleType::CLOSE_WEBTRANSPORT_SESSION: {
       if (web_transport_ == nullptr) {
@@ -1541,12 +1544,12 @@
   QuicByteCount prefix_size = 0;
   switch (spdy_session_->http_datagram_support()) {
     case HttpDatagramSupport::kDraft04:
-    case HttpDatagramSupport::kDraft09:
+    case HttpDatagramSupport::kRfc:
       prefix_size =
           QuicDataWriter::GetVarInt62Len(id() / kHttpDatagramStreamIdDivisor);
       break;
     case HttpDatagramSupport::kNone:
-    case HttpDatagramSupport::kDraft04And09:
+    case HttpDatagramSupport::kRfcAndDraft04:
       QUIC_BUG(GetMaxDatagramSize called with no datagram support)
           << "GetMaxDatagramSize() called when no HTTP/3 datagram support has "
              "been negotiated.  Support value: "
diff --git a/quiche/quic/core/http/quic_spdy_stream_test.cc b/quiche/quic/core/http/quic_spdy_stream_test.cc
index 960060e..81f6d0a 100644
--- a/quiche/quic/core/http/quic_spdy_stream_test.cc
+++ b/quiche/quic/core/http/quic_spdy_stream_test.cc
@@ -3078,18 +3078,18 @@
   EXPECT_FALSE(stream_->write_side_closed());
 }
 
-TEST_P(QuicSpdyStreamTest, ProcessOutgoingWebTransportHeadersDatagramDraft04) {
+TEST_P(QuicSpdyStreamTest, ProcessOutgoingWebTransportHeaders) {
   if (!UsesHttp3()) {
     return;
   }
 
   InitializeWithPerspective(kShouldProcessData, Perspective::IS_CLIENT);
-  session_->set_local_http_datagram_support(HttpDatagramSupport::kDraft04);
+  session_->set_local_http_datagram_support(HttpDatagramSupport::kRfc);
   session_->EnableWebTransport();
   session_->OnSetting(SETTINGS_ENABLE_CONNECT_PROTOCOL, 1);
   QuicSpdySessionPeer::EnableWebTransport(session_.get());
   QuicSpdySessionPeer::SetHttpDatagramSupport(session_.get(),
-                                              HttpDatagramSupport::kDraft04);
+                                              HttpDatagramSupport::kRfc);
 
   EXPECT_CALL(*stream_, WriteHeadersMock(false));
   EXPECT_CALL(*session_, WritevData(stream_->id(), _, _, _, _, _))
@@ -3103,17 +3103,17 @@
   EXPECT_EQ(stream_->id(), stream_->web_transport()->id());
 }
 
-TEST_P(QuicSpdyStreamTest, ProcessIncomingWebTransportHeadersDatagramDraft04) {
+TEST_P(QuicSpdyStreamTest, ProcessIncomingWebTransportHeaders) {
   if (!UsesHttp3()) {
     return;
   }
 
   Initialize(kShouldProcessData);
-  session_->set_local_http_datagram_support(HttpDatagramSupport::kDraft04);
+  session_->set_local_http_datagram_support(HttpDatagramSupport::kRfc);
   session_->EnableWebTransport();
   QuicSpdySessionPeer::EnableWebTransport(session_.get());
   QuicSpdySessionPeer::SetHttpDatagramSupport(session_.get(),
-                                              HttpDatagramSupport::kDraft04);
+                                              HttpDatagramSupport::kRfc);
 
   headers_[":method"] = "CONNECT";
   headers_[":protocol"] = "webtransport";
@@ -3134,9 +3134,9 @@
     return;
   }
   InitializeWithPerspective(kShouldProcessData, Perspective::IS_CLIENT);
-  session_->set_local_http_datagram_support(HttpDatagramSupport::kDraft09);
+  session_->set_local_http_datagram_support(HttpDatagramSupport::kRfc);
   QuicSpdySessionPeer::SetHttpDatagramSupport(session_.get(),
-                                              HttpDatagramSupport::kDraft09);
+                                              HttpDatagramSupport::kRfc);
   headers_[":method"] = "CONNECT";
   headers_[":protocol"] = "webtransport";
   ProcessHeaders(false, headers_);
@@ -3174,9 +3174,9 @@
     return;
   }
   Initialize(kShouldProcessData);
-  session_->set_local_http_datagram_support(HttpDatagramSupport::kDraft09);
+  session_->set_local_http_datagram_support(HttpDatagramSupport::kRfc);
   QuicSpdySessionPeer::SetHttpDatagramSupport(session_.get(),
-                                              HttpDatagramSupport::kDraft09);
+                                              HttpDatagramSupport::kRfc);
   std::string http_datagram_payload = {1, 2, 3, 4, 5, 6};
   EXPECT_CALL(*connection_, SendMessage(1, _, false))
       .WillOnce(Return(MESSAGE_STATUS_SUCCESS));
@@ -3189,9 +3189,9 @@
     return;
   }
   Initialize(kShouldProcessData);
-  session_->set_local_http_datagram_support(HttpDatagramSupport::kDraft09);
+  session_->set_local_http_datagram_support(HttpDatagramSupport::kRfc);
   QuicSpdySessionPeer::SetHttpDatagramSupport(session_.get(),
-                                              HttpDatagramSupport::kDraft09);
+                                              HttpDatagramSupport::kRfc);
   EXPECT_GT(stream_->GetMaxDatagramSize(), 512u);
 }
 
@@ -3200,9 +3200,9 @@
     return;
   }
   Initialize(kShouldProcessData);
-  session_->set_local_http_datagram_support(HttpDatagramSupport::kDraft09);
+  session_->set_local_http_datagram_support(HttpDatagramSupport::kRfc);
   QuicSpdySessionPeer::SetHttpDatagramSupport(session_.get(),
-                                              HttpDatagramSupport::kDraft09);
+                                              HttpDatagramSupport::kRfc);
   SavingHttp3DatagramVisitor h3_datagram_visitor;
   stream_->RegisterHttp3DatagramVisitor(&h3_datagram_visitor);
   SavingConnectIpVisitor connect_ip_visitor;
@@ -3212,7 +3212,7 @@
   ProcessHeaders(/*fin=*/false, headers_);
   // Datagram capsule.
   std::string http_datagram_payload = {1, 2, 3, 4, 5, 6};
-  stream_->OnCapsule(Capsule::DatagramWithoutContext(http_datagram_payload));
+  stream_->OnCapsule(Capsule::Datagram(http_datagram_payload));
   EXPECT_THAT(h3_datagram_visitor.received_h3_datagrams(),
               ElementsAre(SavingHttp3DatagramVisitor::SavedHttp3Datagram{
                   stream_->id(), http_datagram_payload}));
diff --git a/quiche/quic/masque/masque_client_session.h b/quiche/quic/masque/masque_client_session.h
index 66c7e2e..43609dc 100644
--- a/quiche/quic/masque/masque_client_session.h
+++ b/quiche/quic/masque/masque_client_session.h
@@ -125,7 +125,7 @@
   };
 
   HttpDatagramSupport LocalHttpDatagramSupport() override {
-    return HttpDatagramSupport::kDraft09;
+    return HttpDatagramSupport::kRfc;
   }
 
   const ConnectUdpClientState* GetOrCreateConnectUdpClientState(
diff --git a/quiche/quic/masque/masque_server_session.h b/quiche/quic/masque/masque_server_session.h
index 20b85a1..f26ef4f 100644
--- a/quiche/quic/masque/masque_server_session.h
+++ b/quiche/quic/masque/masque_server_session.h
@@ -93,7 +93,7 @@
   // From QuicSpdySession.
   bool OnSettingsFrame(const SettingsFrame& frame) override;
   HttpDatagramSupport LocalHttpDatagramSupport() override {
-    return HttpDatagramSupport::kDraft09;
+    return HttpDatagramSupport::kRfc;
   }
 
   MasqueServerBackend* masque_server_backend_;  // Unowned.