Accept new WebTransport sessions over HTTP/3.

PiperOrigin-RevId: 362614422
Change-Id: I08c3572279ac3bee29d1da62dde520808fb1321e
diff --git a/quic/core/http/quic_spdy_session.cc b/quic/core/http/quic_spdy_session.cc
index 647cef7..a7f3df8 100644
--- a/quic/core/http/quic_spdy_session.cc
+++ b/quic/core/http/quic_spdy_session.cc
@@ -1797,6 +1797,21 @@
          peer_supports_webtransport_;
 }
 
+WebTransportHttp3* QuicSpdySession::GetWebTransportSession(
+    WebTransportSessionId id) {
+  if (!SupportsWebTransport()) {
+    return nullptr;
+  }
+  if (!IsValidWebTransportSessionId(id, version())) {
+    return nullptr;
+  }
+  QuicSpdyStream* connect_stream = GetOrCreateSpdyDataStream(id);
+  if (connect_stream == nullptr) {
+    return nullptr;
+  }
+  return connect_stream->web_transport();
+}
+
 #undef ENDPOINT  // undef for jumbo builds
 
 }  // namespace quic
diff --git a/quic/core/http/quic_spdy_session.h b/quic/core/http/quic_spdy_session.h
index bd5cb00..bbd97fc 100644
--- a/quic/core/http/quic_spdy_session.h
+++ b/quic/core/http/quic_spdy_session.h
@@ -461,6 +461,10 @@
   // Indicates whether the HTTP/3 session supports WebTransport.
   bool SupportsWebTransport();
 
+  // Returns a WebTransport session by its session ID.  Returns nullptr if no
+  // session is associated with the given ID.
+  WebTransportHttp3* GetWebTransportSession(WebTransportSessionId id);
+
  protected:
   // Override CreateIncomingStream(), CreateOutgoingBidirectionalStream() and
   // CreateOutgoingUnidirectionalStream() with QuicSpdyStream return type to
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc
index c532322..db3b98e 100644
--- a/quic/core/http/quic_spdy_stream.cc
+++ b/quic/core/http/quic_spdy_stream.cc
@@ -5,6 +5,7 @@
 #include "quic/core/http/quic_spdy_stream.h"
 
 #include <limits>
+#include <memory>
 #include <string>
 #include <utility>
 
@@ -15,6 +16,7 @@
 #include "quic/core/http/http_decoder.h"
 #include "quic/core/http/quic_spdy_session.h"
 #include "quic/core/http/spdy_utils.h"
+#include "quic/core/http/web_transport_http3.h"
 #include "quic/core/qpack/qpack_decoder.h"
 #include "quic/core/qpack/qpack_encoder.h"
 #include "quic/core/quic_utils.h"
@@ -287,6 +289,8 @@
                       nullptr);
   }
 
+  MaybeProcessSentWebTransportHeaders(header_block);
+
   size_t bytes_written =
       WriteHeadersImpl(std::move(header_block), fin, std::move(ack_listener));
   if (!VersionUsesHttp3(transport_version()) && fin) {
@@ -648,6 +652,8 @@
   headers_decompressed_ = true;
   header_list_ = header_list;
 
+  MaybeProcessReceivedWebTransportHeaders();
+
   if (VersionUsesHttp3(transport_version())) {
     if (fin) {
       OnStreamFrame(QuicStreamFrame(id(), /* fin = */ true,
@@ -1163,5 +1169,64 @@
   return encoded_headers.size();
 }
 
+void QuicSpdyStream::MaybeProcessReceivedWebTransportHeaders() {
+  if (!spdy_session_->SupportsWebTransport()) {
+    return;
+  }
+  if (session()->perspective() != Perspective::IS_SERVER) {
+    return;
+  }
+  QUICHE_DCHECK(IsValidWebTransportSessionId(id(), version()));
+
+  std::string method;
+  std::string protocol;
+  for (const auto& header : header_list_) {
+    const std::string& header_name = header.first;
+    const std::string& header_value = header.second;
+    if (header_name == ":method") {
+      if (!method.empty() || header_value.empty()) {
+        return;
+      }
+      method = header_value;
+    }
+    if (header_name == ":protocol") {
+      if (!protocol.empty() || header_value.empty()) {
+        return;
+      }
+      protocol = header_value;
+    }
+  }
+
+  if (method != "CONNECT" || protocol != "webtransport") {
+    return;
+  }
+
+  web_transport_ =
+      std::make_unique<WebTransportHttp3>(spdy_session_, this, id());
+}
+
+void QuicSpdyStream::MaybeProcessSentWebTransportHeaders(
+    spdy::SpdyHeaderBlock& headers) {
+  if (!spdy_session_->SupportsWebTransport()) {
+    return;
+  }
+  if (session()->perspective() != Perspective::IS_CLIENT) {
+    return;
+  }
+  QUICHE_DCHECK(IsValidWebTransportSessionId(id(), version()));
+
+  const auto method_it = headers.find(":method");
+  const auto protocol_it = headers.find(":protocol");
+  if (method_it == headers.end() || protocol_it == headers.end()) {
+    return;
+  }
+  if (method_it->second != "CONNECT" && protocol_it->second != "webtransport") {
+    return;
+  }
+
+  web_transport_ =
+      std::make_unique<WebTransportHttp3>(spdy_session_, this, id());
+}
+
 #undef ENDPOINT  // undef for jumbo builds
 }  // namespace quic
diff --git a/quic/core/http/quic_spdy_stream.h b/quic/core/http/quic_spdy_stream.h
index 60d5e39..f3ec720 100644
--- a/quic/core/http/quic_spdy_stream.h
+++ b/quic/core/http/quic_spdy_stream.h
@@ -13,6 +13,7 @@
 
 #include <cstddef>
 #include <list>
+#include <memory>
 #include <string>
 
 #include "absl/strings/string_view.h"
@@ -28,6 +29,7 @@
 #include "quic/platform/api/quic_flags.h"
 #include "quic/platform/api/quic_socket_address.h"
 #include "spdy/core/spdy_framer.h"
+#include "spdy/core/spdy_header_block.h"
 
 namespace quic {
 
@@ -37,6 +39,7 @@
 }  // namespace test
 
 class QuicSpdySession;
+class WebTransportHttp3;
 
 // A QUIC stream that can send and receive HTTP2 (SPDY) headers.
 class QUIC_EXPORT_PRIVATE QuicSpdyStream
@@ -222,6 +225,9 @@
   // |last_sent_urgency_| is different from current priority.
   void MaybeSendPriorityUpdateFrame() override;
 
+  // Returns the WebTransport session owned by this stream, if one exists.
+  WebTransportHttp3* web_transport() { return web_transport_.get(); }
+
  protected:
   // Called when the received headers are too large. By default this will
   // reset the stream.
@@ -279,6 +285,9 @@
   QuicByteCount GetNumFrameHeadersInInterval(QuicStreamOffset offset,
                                              QuicByteCount data_length) const;
 
+  void MaybeProcessSentWebTransportHeaders(spdy::SpdyHeaderBlock& headers);
+  void MaybeProcessReceivedWebTransportHeaders();
+
   QuicSpdySession* spdy_session_;
 
   bool on_body_available_called_because_sequencer_is_closed_;
@@ -339,6 +348,10 @@
   // Urgency value sent in the last PRIORITY_UPDATE frame, or default urgency
   // defined by the spec if no PRIORITY_UPDATE frame has been sent.
   int last_sent_urgency_;
+
+  // If this stream is a WebTransport extended CONNECT stream, contains the
+  // WebTransport session associated with this stream.
+  std::unique_ptr<WebTransportHttp3> web_transport_;
 };
 
 }  // namespace quic
diff --git a/quic/core/http/quic_spdy_stream_test.cc b/quic/core/http/quic_spdy_stream_test.cc
index 0e35c28..424fa0a 100644
--- a/quic/core/http/quic_spdy_stream_test.cc
+++ b/quic/core/http/quic_spdy_stream_test.cc
@@ -15,6 +15,7 @@
 #include "quic/core/crypto/null_encrypter.h"
 #include "quic/core/http/http_encoder.h"
 #include "quic/core/http/spdy_utils.h"
+#include "quic/core/http/web_transport_http3.h"
 #include "quic/core/quic_connection.h"
 #include "quic/core/quic_stream_sequencer_buffer.h"
 #include "quic/core/quic_utils.h"
@@ -255,6 +256,8 @@
     return &crypto_stream_;
   }
 
+  bool ShouldNegotiateWebTransport() override { return true; }
+
  private:
   StrictMock<TestCryptoStream> crypto_stream_;
 };
@@ -3071,6 +3074,47 @@
   }
 }
 
+TEST_P(QuicSpdyStreamTest, ProcessOutgoingWebTransportHeaders) {
+  if (!UsesHttp3()) {
+    return;
+  }
+
+  InitializeWithPerspective(kShouldProcessData, Perspective::IS_CLIENT);
+  QuicSpdySessionPeer::EnableWebTransport(*session_);
+
+  EXPECT_CALL(*stream_, WriteHeadersMock(false));
+  EXPECT_CALL(*session_, WritevData(stream_->id(), _, _, _, _, _))
+      .Times(AnyNumber());
+
+  spdy::SpdyHeaderBlock headers;
+  headers[":method"] = "CONNECT";
+  headers[":protocol"] = "webtransport";
+  stream_->WriteHeaders(std::move(headers), /*fin=*/false, nullptr);
+  ASSERT_TRUE(stream_->web_transport() != nullptr);
+  EXPECT_EQ(stream_->id(), stream_->web_transport()->id());
+}
+
+TEST_P(QuicSpdyStreamTest, ProcessIncomingWebTransportHeaders) {
+  if (!UsesHttp3()) {
+    return;
+  }
+
+  Initialize(kShouldProcessData);
+  QuicSpdySessionPeer::EnableWebTransport(*session_);
+
+  headers_[":method"] = "CONNECT";
+  headers_[":protocol"] = "webtransport";
+
+  stream_->OnStreamHeadersPriority(
+      spdy::SpdyStreamPrecedence(kV3HighestPriority));
+  ProcessHeaders(false, headers_);
+  EXPECT_EQ("", stream_->data());
+  EXPECT_FALSE(stream_->header_list().empty());
+  EXPECT_FALSE(stream_->IsDoneReading());
+  ASSERT_TRUE(stream_->web_transport() != nullptr);
+  EXPECT_EQ(stream_->id(), stream_->web_transport()->id());
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/http/web_transport_http3.cc b/quic/core/http/web_transport_http3.cc
new file mode 100644
index 0000000..0c7c9a8
--- /dev/null
+++ b/quic/core/http/web_transport_http3.cc
@@ -0,0 +1,75 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quic/core/http/web_transport_http3.h"
+
+#include <memory>
+
+#include "quic/core/http/quic_spdy_session.h"
+#include "quic/core/http/quic_spdy_stream.h"
+
+namespace quic {
+
+namespace {
+class QUIC_EXPORT_PRIVATE NoopWebTransportVisitor : public WebTransportVisitor {
+  void OnSessionReady() override {}
+  void OnIncomingBidirectionalStreamAvailable() override {}
+  void OnIncomingUnidirectionalStreamAvailable() override {}
+  void OnDatagramReceived(absl::string_view /*datagram*/) override {}
+  void OnCanCreateNewOutgoingBidirectionalStream() override {}
+  void OnCanCreateNewOutgoingUnidirectionalStream() override {}
+};
+}  // namespace
+
+WebTransportHttp3::WebTransportHttp3(QuicSpdySession* session,
+                                     QuicSpdyStream* connect_stream,
+                                     WebTransportSessionId id)
+    : session_(session),
+      connect_stream_(connect_stream),
+      id_(id),
+      visitor_(std::make_unique<NoopWebTransportVisitor>()) {}
+
+void WebTransportHttp3::HeadersReceived(
+    const spdy::SpdyHeaderBlock& /*headers*/) {
+  ready_ = true;
+  visitor_->OnSessionReady();
+}
+
+WebTransportStream* WebTransportHttp3::AcceptIncomingBidirectionalStream() {
+  // TODO(vasilvv): implement this.
+  return nullptr;
+}
+WebTransportStream* WebTransportHttp3::AcceptIncomingUnidirectionalStream() {
+  // TODO(vasilvv): implement this.
+  return nullptr;
+}
+
+bool WebTransportHttp3::CanOpenNextOutgoingBidirectionalStream() {
+  // TODO(vasilvv): implement this.
+  return false;
+}
+bool WebTransportHttp3::CanOpenNextOutgoingUnidirectionalStream() {
+  // TODO(vasilvv): implement this.
+  return false;
+}
+WebTransportStream* WebTransportHttp3::OpenOutgoingBidirectionalStream() {
+  // TODO(vasilvv): implement this.
+  return nullptr;
+}
+WebTransportStream* WebTransportHttp3::OpenOutgoingUnidirectionalStream() {
+  // TODO(vasilvv): implement this.
+  return nullptr;
+}
+
+MessageStatus WebTransportHttp3::SendOrQueueDatagram(
+    QuicMemSlice /*datagram*/) {
+  // TODO(vasilvv): implement this.
+  return MessageStatus::MESSAGE_STATUS_UNSUPPORTED;
+}
+void WebTransportHttp3::SetDatagramMaxTimeInQueue(
+    QuicTime::Delta /*max_time_in_queue*/) {
+  // TODO(vasilvv): implement this.
+}
+
+}  // namespace quic
diff --git a/quic/core/http/web_transport_http3.h b/quic/core/http/web_transport_http3.h
new file mode 100644
index 0000000..7176411
--- /dev/null
+++ b/quic/core/http/web_transport_http3.h
@@ -0,0 +1,62 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_WEB_TRANSPORT_HTTP3_H_
+#define QUICHE_QUIC_CORE_HTTP_WEB_TRANSPORT_HTTP3_H_
+
+#include <memory>
+
+#include "quic/core/quic_types.h"
+#include "quic/core/web_transport_interface.h"
+#include "spdy/core/spdy_header_block.h"
+
+namespace quic {
+
+class QuicSpdySession;
+class QuicSpdyStream;
+
+// A session of WebTransport over HTTP/3.  The session is owned by
+// QuicSpdyStream object for the CONNECT stream that established it.
+//
+// WebTransport over HTTP/3 specification:
+// <https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3>
+class QUIC_EXPORT_PRIVATE WebTransportHttp3 : public WebTransportSession {
+ public:
+  WebTransportHttp3(QuicSpdySession* session,
+                    QuicSpdyStream* connect_stream,
+                    WebTransportSessionId id);
+
+  void HeadersReceived(const spdy::SpdyHeaderBlock& headers);
+  void SetVisitor(std::unique_ptr<WebTransportVisitor> visitor) {
+    visitor_ = std::move(visitor);
+  }
+
+  WebTransportSessionId id() { return id_; }
+
+  // Return the earliest incoming stream that has been received by the session
+  // but has not been accepted.  Returns nullptr if there are no incoming
+  // streams.
+  WebTransportStream* AcceptIncomingBidirectionalStream() override;
+  WebTransportStream* AcceptIncomingUnidirectionalStream() override;
+
+  bool CanOpenNextOutgoingBidirectionalStream() override;
+  bool CanOpenNextOutgoingUnidirectionalStream() override;
+  WebTransportStream* OpenOutgoingBidirectionalStream() override;
+  WebTransportStream* OpenOutgoingUnidirectionalStream() override;
+
+  MessageStatus SendOrQueueDatagram(QuicMemSlice datagram) override;
+  void SetDatagramMaxTimeInQueue(QuicTime::Delta max_time_in_queue) override;
+
+ private:
+  const QuicSpdySession* session_;        // Unowned.
+  const QuicSpdyStream* connect_stream_;  // Unowned.
+  const WebTransportSessionId id_;
+  // |ready_| is set to true when the peer has seen both sets of headers.
+  bool ready_ = false;
+  std::unique_ptr<WebTransportVisitor> visitor_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_HTTP_WEB_TRANSPORT_HTTP3_H_
diff --git a/quic/core/quic_types.h b/quic/core/quic_types.h
index 0e13f56..83404c5 100644
--- a/quic/core/quic_types.h
+++ b/quic/core/quic_types.h
@@ -7,6 +7,7 @@
 
 #include <array>
 #include <cstddef>
+#include <cstdint>
 #include <map>
 #include <ostream>
 #include <vector>
@@ -43,6 +44,9 @@
 using DiversificationNonce = std::array<char, 32>;
 using PacketTimeVector = std::vector<std::pair<QuicPacketNumber, QuicTime>>;
 
+// WebTransport session IDs are stream IDs.
+using WebTransportSessionId = uint64_t;
+
 enum : size_t { kQuicPathFrameBufferSize = 8 };
 using QuicPathFrameBuffer = std::array<uint8_t, kQuicPathFrameBufferSize>;
 
@@ -708,10 +712,10 @@
 
 // Indicates the fate of a serialized packet in WritePacket().
 enum SerializedPacketFate : uint8_t {
-  DISCARD,         // Discard the packet.
-  COALESCE,        // Try to coalesce packet.
-  BUFFER,          // Buffer packet in buffered_packets_.
-  SEND_TO_WRITER,  // Send packet to writer.
+  DISCARD,                     // Discard the packet.
+  COALESCE,                    // Try to coalesce packet.
+  BUFFER,                      // Buffer packet in buffered_packets_.
+  SEND_TO_WRITER,              // Send packet to writer.
   LEGACY_VERSION_ENCAPSULATE,  // Perform Legacy Version Encapsulation on this
                                // packet.
 };
diff --git a/quic/core/quic_utils.cc b/quic/core/quic_utils.cc
index c26b8dd..1f190e0 100644
--- a/quic/core/quic_utils.cc
+++ b/quic/core/quic_utils.cc
@@ -7,6 +7,7 @@
 #include <algorithm>
 #include <cstdint>
 #include <cstring>
+#include <limits>
 #include <string>
 
 #include "absl/base/macros.h"
@@ -21,6 +22,7 @@
 #include "quic/platform/api/quic_flags.h"
 #include "quic/platform/api/quic_prefetch.h"
 #include "quic/platform/api/quic_uint128.h"
+#include "common/platform/api/quiche_logging.h"
 #include "common/quiche_endian.h"
 
 namespace quic {
@@ -686,5 +688,13 @@
   }
 }
 
+bool IsValidWebTransportSessionId(WebTransportSessionId id,
+                                  ParsedQuicVersion version) {
+  QUICHE_DCHECK(version.UsesHttp3());
+  return (id <= std::numeric_limits<QuicStreamId>::max()) &&
+         QuicUtils::IsBidirectionalStreamId(id, version) &&
+         QuicUtils::IsClientInitiatedStreamId(version.transport_version, id);
+}
+
 #undef RETURN_STRING_LITERAL  // undef for jumbo builds
 }  // namespace quic
diff --git a/quic/core/quic_utils.h b/quic/core/quic_utils.h
index 9175117..463b63f 100644
--- a/quic/core/quic_utils.h
+++ b/quic/core/quic_utils.h
@@ -239,6 +239,11 @@
   static bool IsProbingFrame(QuicFrameType type);
 };
 
+// Returns true if the specific ID is a valid WebTransport session ID that our
+// implementation can process.
+bool IsValidWebTransportSessionId(WebTransportSessionId id,
+                                  ParsedQuicVersion transport_version);
+
 template <typename Mask>
 class QUIC_EXPORT_PRIVATE BitMask {
  public:
diff --git a/quic/test_tools/quic_spdy_session_peer.cc b/quic/test_tools/quic_spdy_session_peer.cc
index d41ddfd..aee5d12 100644
--- a/quic/test_tools/quic_spdy_session_peer.cc
+++ b/quic/test_tools/quic_spdy_session_peer.cc
@@ -7,7 +7,9 @@
 #include "quic/core/http/quic_spdy_session.h"
 #include "quic/core/qpack/qpack_receive_stream.h"
 #include "quic/core/quic_utils.h"
+#include "quic/platform/api/quic_flags.h"
 #include "quic/test_tools/quic_session_peer.h"
+#include "common/platform/api/quiche_logging.h"
 
 namespace quic {
 namespace test {
@@ -115,5 +117,13 @@
   session->h3_datagram_supported_ = h3_datagram_supported;
 }
 
+// static
+void QuicSpdySessionPeer::EnableWebTransport(QuicSpdySession& session) {
+  SetQuicReloadableFlag(quic_h3_datagram, true);
+  QUICHE_DCHECK(session.WillNegotiateWebTransport());
+  session.h3_datagram_supported_ = true;
+  session.peer_supports_webtransport_ = true;
+}
+
 }  // namespace test
 }  // namespace quic
diff --git a/quic/test_tools/quic_spdy_session_peer.h b/quic/test_tools/quic_spdy_session_peer.h
index 4ad0367..bbcb696 100644
--- a/quic/test_tools/quic_spdy_session_peer.h
+++ b/quic/test_tools/quic_spdy_session_peer.h
@@ -59,6 +59,7 @@
       QuicSpdySession* session);
   static void SetH3DatagramSupported(QuicSpdySession* session,
                                      bool h3_datagram_supported);
+  static void EnableWebTransport(QuicSpdySession& session);
 };
 
 }  // namespace test