Implement sending and receiving DRAIN_WEBTRANSPORT_SESSION

PiperOrigin-RevId: 540692201
diff --git a/quiche/common/capsule.cc b/quiche/common/capsule.cc
index 4c5ad5a..66d4a66 100644
--- a/quiche/common/capsule.cc
+++ b/quiche/common/capsule.cc
@@ -36,6 +36,8 @@
       return "LEGACY_DATAGRAM_WITHOUT_CONTEXT";
     case CapsuleType::CLOSE_WEBTRANSPORT_SESSION:
       return "CLOSE_WEBTRANSPORT_SESSION";
+    case CapsuleType::DRAIN_WEBTRANSPORT_SESSION:
+      return "DRAIN_WEBTRANSPORT_SESSION";
     case CapsuleType::ADDRESS_REQUEST:
       return "ADDRESS_REQUEST";
     case CapsuleType::ADDRESS_ASSIGN:
@@ -129,6 +131,10 @@
                       ",error_message=\"", error_message, "\")");
 }
 
+std::string DrainWebTransportSessionCapsule::ToString() const {
+  return "DRAIN_WEBTRANSPORT_SESSION()";
+}
+
 std::string AddressRequestCapsule::ToString() const {
   std::string rv = "ADDRESS_REQUEST[";
   for (auto requested_address : requested_addresses) {
@@ -293,6 +299,8 @@
           WireUint32(capsule.close_web_transport_session_capsule().error_code),
           WireBytes(
               capsule.close_web_transport_session_capsule().error_message));
+    case CapsuleType::DRAIN_WEBTRANSPORT_SESSION:
+      return SerializeCapsuleFields(capsule.capsule_type(), allocator);
     case CapsuleType::ADDRESS_REQUEST:
       return SerializeCapsuleFields(
           capsule.capsule_type(), allocator,
@@ -414,6 +422,8 @@
       capsule.error_message = reader.ReadRemainingPayload();
       return Capsule(std::move(capsule));
     }
+    case CapsuleType::DRAIN_WEBTRANSPORT_SESSION:
+      return Capsule(DrainWebTransportSessionCapsule());
     case CapsuleType::ADDRESS_REQUEST: {
       AddressRequestCapsule capsule;
       while (!reader.IsDoneReading()) {
diff --git a/quiche/common/capsule.h b/quiche/common/capsule.h
index 08bde5b..7220ee4 100644
--- a/quiche/common/capsule.h
+++ b/quiche/common/capsule.h
@@ -13,6 +13,7 @@
 #include "absl/strings/string_view.h"
 #include "absl/types/optional.h"
 #include "absl/types/variant.h"
+#include "quiche/common/platform/api/quiche_export.h"
 #include "quiche/common/platform/api/quiche_logging.h"
 #include "quiche/common/quiche_buffer_allocator.h"
 #include "quiche/common/quiche_ip_address.h"
@@ -29,6 +30,7 @@
 
   // <https://datatracker.ietf.org/doc/draft-ietf-webtrans-http3/>
   CLOSE_WEBTRANSPORT_SESSION = 0x2843,
+  DRAIN_WEBTRANSPORT_SESSION = 0x78ae,
 
   // draft-ietf-masque-connect-ip-03.
   ADDRESS_ASSIGN = 0x1ECA6A00,
@@ -106,6 +108,13 @@
            error_message == other.error_message;
   }
 };
+struct QUICHE_EXPORT DrainWebTransportSessionCapsule {
+  std::string ToString() const;
+  CapsuleType capsule_type() const {
+    return CapsuleType::DRAIN_WEBTRANSPORT_SESSION;
+  }
+  bool operator==(const DrainWebTransportSessionCapsule&) const { return true; }
+};
 
 // MASQUE CONNECT-IP.
 struct QUICHE_EXPORT PrefixWithId {
@@ -320,7 +329,8 @@
  private:
   absl::variant<DatagramCapsule, LegacyDatagramCapsule,
                 LegacyDatagramWithoutContextCapsule,
-                CloseWebTransportSessionCapsule, AddressRequestCapsule,
+                CloseWebTransportSessionCapsule,
+                DrainWebTransportSessionCapsule, AddressRequestCapsule,
                 AddressAssignCapsule, RouteAdvertisementCapsule,
                 WebTransportStreamDataCapsule, WebTransportResetStreamCapsule,
                 WebTransportStopSendingCapsule, WebTransportMaxStreamsCapsule,
diff --git a/quiche/common/capsule_test.cc b/quiche/common/capsule_test.cc
index 5ed4d1a..ae55aaf 100644
--- a/quiche/common/capsule_test.cc
+++ b/quiche/common/capsule_test.cc
@@ -136,6 +136,20 @@
   TestSerialization(expected_capsule, capsule_fragment);
 }
 
+TEST_F(CapsuleTest, DrainWebTransportStreamCapsule) {
+  std::string capsule_fragment = absl::HexStringToBytes(
+      "800078ae"  // DRAIN_WEBTRANSPORT_STREAM capsule type
+      "00"        // capsule length
+  );
+  Capsule expected_capsule = Capsule(DrainWebTransportSessionCapsule());
+  {
+    EXPECT_CALL(visitor_, OnCapsule(expected_capsule));
+    ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment));
+  }
+  ValidateParserIsEmpty();
+  TestSerialization(expected_capsule, capsule_fragment);
+}
+
 TEST_F(CapsuleTest, AddressAssignCapsule) {
   std::string capsule_fragment = absl::HexStringToBytes(
       "9ECA6A00"  // ADDRESS_ASSIGN capsule type
diff --git a/quiche/common/wire_serialization.h b/quiche/common/wire_serialization.h
index 89a54f2..7cc2596 100644
--- a/quiche/common/wire_serialization.h
+++ b/quiche/common/wire_serialization.h
@@ -347,6 +347,10 @@
   QUICHE_RETURN_IF_ERROR(SerializeIntoWriterCore(writer, argno, data1));
   return SerializeIntoWriterCore(writer, argno + 1, rest...);
 }
+
+inline absl::Status SerializeIntoWriterCore(QuicheDataWriter&, int) {
+  return absl::OkStatus();
+}
 }  // namespace wire_serialization_internal
 
 // SerializeIntoWriter(writer, d1, d2, ... dN) serializes all of supplied data
@@ -369,6 +373,7 @@
 size_t ComputeLengthOnWire(T1 data1, Ts... rest) {
   return data1.GetLengthOnWire() + ComputeLengthOnWire(rest...);
 }
+inline size_t ComputeLengthOnWire() { return 0; }
 
 // SerializeIntoBuffer(allocator, d1, d2, ... dN) computes the length required
 // to store the supplied data, allocates the buffer of appropriate size using
diff --git a/quiche/common/wire_serialization_test.cc b/quiche/common/wire_serialization_test.cc
index b1dea91..9abdda9 100644
--- a/quiche/common/wire_serialization_test.cc
+++ b/quiche/common/wire_serialization_test.cc
@@ -252,5 +252,7 @@
 #endif
 }
 
+TEST(SerializationTest, Empty) { ExpectEncodingHex("nothing", ""); }
+
 }  // namespace
 }  // namespace quiche::test