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