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