1 // Copyright 2021 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "quiche/quic/core/http/web_transport_http3.h"
6
7 #include <limits>
8 #include <memory>
9 #include <optional>
10
11
12 #include "absl/strings/string_view.h"
13 #include "quiche/quic/core/http/quic_spdy_session.h"
14 #include "quiche/quic/core/http/quic_spdy_stream.h"
15 #include "quiche/quic/core/quic_data_reader.h"
16 #include "quiche/quic/core/quic_data_writer.h"
17 #include "quiche/quic/core/quic_error_codes.h"
18 #include "quiche/quic/core/quic_stream.h"
19 #include "quiche/quic/core/quic_types.h"
20 #include "quiche/quic/core/quic_utils.h"
21 #include "quiche/quic/core/quic_versions.h"
22 #include "quiche/quic/platform/api/quic_bug_tracker.h"
23 #include "quiche/common/capsule.h"
24 #include "quiche/common/platform/api/quiche_logging.h"
25 #include "quiche/web_transport/web_transport.h"
26
27 #define ENDPOINT \
28 (session_->perspective() == Perspective::IS_SERVER ? "Server: " : "Client: ")
29
30 namespace quic {
31
32 namespace {
33 class NoopWebTransportVisitor : public WebTransportVisitor {
OnSessionReady()34 void OnSessionReady() override {}
OnSessionClosed(WebTransportSessionError,const std::string &)35 void OnSessionClosed(WebTransportSessionError /*error_code*/,
36 const std::string& /*error_message*/) override {}
OnIncomingBidirectionalStreamAvailable()37 void OnIncomingBidirectionalStreamAvailable() override {}
OnIncomingUnidirectionalStreamAvailable()38 void OnIncomingUnidirectionalStreamAvailable() override {}
OnDatagramReceived(absl::string_view)39 void OnDatagramReceived(absl::string_view /*datagram*/) override {}
OnCanCreateNewOutgoingBidirectionalStream()40 void OnCanCreateNewOutgoingBidirectionalStream() override {}
OnCanCreateNewOutgoingUnidirectionalStream()41 void OnCanCreateNewOutgoingUnidirectionalStream() override {}
42 };
43 } // namespace
44
WebTransportHttp3(QuicSpdySession * session,QuicSpdyStream * connect_stream,WebTransportSessionId id)45 WebTransportHttp3::WebTransportHttp3(QuicSpdySession* session,
46 QuicSpdyStream* connect_stream,
47 WebTransportSessionId id)
48 : session_(session),
49 connect_stream_(connect_stream),
50 id_(id),
51 visitor_(std::make_unique<NoopWebTransportVisitor>()) {
52 QUICHE_DCHECK(session_->SupportsWebTransport());
53 QUICHE_DCHECK(IsValidWebTransportSessionId(id, session_->version()));
54 QUICHE_DCHECK_EQ(connect_stream_->id(), id);
55 connect_stream_->RegisterHttp3DatagramVisitor(this);
56 }
57
AssociateStream(QuicStreamId stream_id)58 void WebTransportHttp3::AssociateStream(QuicStreamId stream_id) {
59 streams_.insert(stream_id);
60
61 ParsedQuicVersion version = session_->version();
62 if (QuicUtils::IsOutgoingStreamId(version, stream_id,
63 session_->perspective())) {
64 return;
65 }
66 if (QuicUtils::IsBidirectionalStreamId(stream_id, version)) {
67 incoming_bidirectional_streams_.push_back(stream_id);
68 visitor_->OnIncomingBidirectionalStreamAvailable();
69 } else {
70 incoming_unidirectional_streams_.push_back(stream_id);
71 visitor_->OnIncomingUnidirectionalStreamAvailable();
72 }
73 }
74
OnConnectStreamClosing()75 void WebTransportHttp3::OnConnectStreamClosing() {
76 // Copy the stream list before iterating over it, as calls to ResetStream()
77 // can potentially mutate the |session_| list.
78 std::vector<QuicStreamId> streams(streams_.begin(), streams_.end());
79 streams_.clear();
80 for (QuicStreamId id : streams) {
81 session_->ResetStream(id, QUIC_STREAM_WEBTRANSPORT_SESSION_GONE);
82 }
83 connect_stream_->UnregisterHttp3DatagramVisitor();
84
85 MaybeNotifyClose();
86 }
87
CloseSession(WebTransportSessionError error_code,absl::string_view error_message)88 void WebTransportHttp3::CloseSession(WebTransportSessionError error_code,
89 absl::string_view error_message) {
90 if (close_sent_) {
91 QUIC_BUG(WebTransportHttp3 close sent twice)
92 << "Calling WebTransportHttp3::CloseSession() more than once is not "
93 "allowed.";
94 return;
95 }
96 close_sent_ = true;
97
98 // There can be a race between us trying to send our close and peer sending
99 // one. If we received a close, however, we cannot send ours since we already
100 // closed the stream in response.
101 if (close_received_) {
102 QUIC_DLOG(INFO) << "Not sending CLOSE_WEBTRANSPORT_SESSION as we've "
103 "already sent one from peer.";
104 return;
105 }
106
107 error_code_ = error_code;
108 error_message_ = std::string(error_message);
109 QuicConnection::ScopedPacketFlusher flusher(
110 connect_stream_->spdy_session()->connection());
111 connect_stream_->WriteCapsule(
112 quiche::Capsule::CloseWebTransportSession(error_code, error_message),
113 /*fin=*/true);
114 }
115
OnCloseReceived(WebTransportSessionError error_code,absl::string_view error_message)116 void WebTransportHttp3::OnCloseReceived(WebTransportSessionError error_code,
117 absl::string_view error_message) {
118 if (close_received_) {
119 QUIC_BUG(WebTransportHttp3 notified of close received twice)
120 << "WebTransportHttp3::OnCloseReceived() may be only called once.";
121 }
122 close_received_ = true;
123
124 // If the peer has sent a close after we sent our own, keep the local error.
125 if (close_sent_) {
126 QUIC_DLOG(INFO) << "Ignoring received CLOSE_WEBTRANSPORT_SESSION as we've "
127 "already sent our own.";
128 return;
129 }
130
131 error_code_ = error_code;
132 error_message_ = std::string(error_message);
133 connect_stream_->WriteOrBufferBody("", /*fin=*/true);
134 MaybeNotifyClose();
135 }
136
OnConnectStreamFinReceived()137 void WebTransportHttp3::OnConnectStreamFinReceived() {
138 // If we already received a CLOSE_WEBTRANSPORT_SESSION capsule, we don't need
139 // to do anything about receiving a FIN, since we already sent one in
140 // response.
141 if (close_received_) {
142 return;
143 }
144 close_received_ = true;
145 if (close_sent_) {
146 QUIC_DLOG(INFO) << "Ignoring received FIN as we've already sent our close.";
147 return;
148 }
149
150 connect_stream_->WriteOrBufferBody("", /*fin=*/true);
151 MaybeNotifyClose();
152 }
153
CloseSessionWithFinOnlyForTests()154 void WebTransportHttp3::CloseSessionWithFinOnlyForTests() {
155 QUICHE_DCHECK(!close_sent_);
156 close_sent_ = true;
157 if (close_received_) {
158 return;
159 }
160
161 connect_stream_->WriteOrBufferBody("", /*fin=*/true);
162 }
163
HeadersReceived(const spdy::Http2HeaderBlock & headers)164 void WebTransportHttp3::HeadersReceived(const spdy::Http2HeaderBlock& headers) {
165 if (session_->perspective() == Perspective::IS_CLIENT) {
166 int status_code;
167 if (!QuicSpdyStream::ParseHeaderStatusCode(headers, &status_code)) {
168 QUIC_DVLOG(1) << ENDPOINT
169 << "Received WebTransport headers from server without "
170 "a valid status code, rejecting.";
171 rejection_reason_ = WebTransportHttp3RejectionReason::kNoStatusCode;
172 return;
173 }
174 bool valid_status = status_code >= 200 && status_code <= 299;
175 if (!valid_status) {
176 QUIC_DVLOG(1) << ENDPOINT
177 << "Received WebTransport headers from server with "
178 "status code "
179 << status_code << ", rejecting.";
180 rejection_reason_ = WebTransportHttp3RejectionReason::kWrongStatusCode;
181 return;
182 }
183 }
184
185 QUIC_DVLOG(1) << ENDPOINT << "WebTransport session " << id_ << " ready.";
186 ready_ = true;
187 visitor_->OnSessionReady();
188 session_->ProcessBufferedWebTransportStreamsForSession(this);
189 }
190
AcceptIncomingBidirectionalStream()191 WebTransportStream* WebTransportHttp3::AcceptIncomingBidirectionalStream() {
192 while (!incoming_bidirectional_streams_.empty()) {
193 QuicStreamId id = incoming_bidirectional_streams_.front();
194 incoming_bidirectional_streams_.pop_front();
195 QuicSpdyStream* stream = session_->GetOrCreateSpdyDataStream(id);
196 if (stream == nullptr) {
197 // Skip the streams that were reset in between the time they were
198 // receieved and the time the client has polled for them.
199 continue;
200 }
201 return stream->web_transport_stream();
202 }
203 return nullptr;
204 }
205
AcceptIncomingUnidirectionalStream()206 WebTransportStream* WebTransportHttp3::AcceptIncomingUnidirectionalStream() {
207 while (!incoming_unidirectional_streams_.empty()) {
208 QuicStreamId id = incoming_unidirectional_streams_.front();
209 incoming_unidirectional_streams_.pop_front();
210 QuicStream* stream = session_->GetOrCreateStream(id);
211 if (stream == nullptr) {
212 // Skip the streams that were reset in between the time they were
213 // receieved and the time the client has polled for them.
214 continue;
215 }
216 return static_cast<WebTransportHttp3UnidirectionalStream*>(stream)
217 ->interface();
218 }
219 return nullptr;
220 }
221
CanOpenNextOutgoingBidirectionalStream()222 bool WebTransportHttp3::CanOpenNextOutgoingBidirectionalStream() {
223 return session_->CanOpenOutgoingBidirectionalWebTransportStream(id_);
224 }
CanOpenNextOutgoingUnidirectionalStream()225 bool WebTransportHttp3::CanOpenNextOutgoingUnidirectionalStream() {
226 return session_->CanOpenOutgoingUnidirectionalWebTransportStream(id_);
227 }
OpenOutgoingBidirectionalStream()228 WebTransportStream* WebTransportHttp3::OpenOutgoingBidirectionalStream() {
229 QuicSpdyStream* stream =
230 session_->CreateOutgoingBidirectionalWebTransportStream(this);
231 if (stream == nullptr) {
232 // If stream cannot be created due to flow control or other errors, return
233 // nullptr.
234 return nullptr;
235 }
236 return stream->web_transport_stream();
237 }
238
OpenOutgoingUnidirectionalStream()239 WebTransportStream* WebTransportHttp3::OpenOutgoingUnidirectionalStream() {
240 WebTransportHttp3UnidirectionalStream* stream =
241 session_->CreateOutgoingUnidirectionalWebTransportStream(this);
242 if (stream == nullptr) {
243 // If stream cannot be created due to flow control, return nullptr.
244 return nullptr;
245 }
246 return stream->interface();
247 }
248
GetStreamById(webtransport::StreamId id)249 webtransport::Stream* WebTransportHttp3::GetStreamById(
250 webtransport::StreamId id) {
251 if (!streams_.contains(id)) {
252 return nullptr;
253 }
254 QuicStream* stream = session_->GetActiveStream(id);
255 const bool bidi = QuicUtils::IsBidirectionalStreamId(
256 id, ParsedQuicVersion::RFCv1()); // Assume IETF QUIC for WebTransport
257 if (bidi) {
258 return static_cast<QuicSpdyStream*>(stream)->web_transport_stream();
259 } else {
260 return static_cast<WebTransportHttp3UnidirectionalStream*>(stream)
261 ->interface();
262 }
263 }
264
SendOrQueueDatagram(absl::string_view datagram)265 webtransport::DatagramStatus WebTransportHttp3::SendOrQueueDatagram(
266 absl::string_view datagram) {
267 return MessageStatusToWebTransportStatus(
268 connect_stream_->SendHttp3Datagram(datagram));
269 }
270
GetMaxDatagramSize() const271 QuicByteCount WebTransportHttp3::GetMaxDatagramSize() const {
272 return connect_stream_->GetMaxDatagramSize();
273 }
274
SetDatagramMaxTimeInQueue(absl::Duration max_time_in_queue)275 void WebTransportHttp3::SetDatagramMaxTimeInQueue(
276 absl::Duration max_time_in_queue) {
277 connect_stream_->SetMaxDatagramTimeInQueue(QuicTimeDelta(max_time_in_queue));
278 }
279
NotifySessionDraining()280 void WebTransportHttp3::NotifySessionDraining() {
281 if (!drain_sent_) {
282 connect_stream_->WriteCapsule(
283 quiche::Capsule(quiche::DrainWebTransportSessionCapsule()));
284 drain_sent_ = true;
285 }
286 }
287
OnHttp3Datagram(QuicStreamId stream_id,absl::string_view payload)288 void WebTransportHttp3::OnHttp3Datagram(QuicStreamId stream_id,
289 absl::string_view payload) {
290 QUICHE_DCHECK_EQ(stream_id, connect_stream_->id());
291 visitor_->OnDatagramReceived(payload);
292 }
293
MaybeNotifyClose()294 void WebTransportHttp3::MaybeNotifyClose() {
295 if (close_notified_) {
296 return;
297 }
298 close_notified_ = true;
299 visitor_->OnSessionClosed(error_code_, error_message_);
300 }
301
OnGoAwayReceived()302 void WebTransportHttp3::OnGoAwayReceived() {
303 if (drain_callback_ != nullptr) {
304 std::move(drain_callback_)();
305 drain_callback_ = nullptr;
306 }
307 }
308
OnDrainSessionReceived()309 void WebTransportHttp3::OnDrainSessionReceived() { OnGoAwayReceived(); }
310
WebTransportHttp3UnidirectionalStream(PendingStream * pending,QuicSpdySession * session)311 WebTransportHttp3UnidirectionalStream::WebTransportHttp3UnidirectionalStream(
312 PendingStream* pending, QuicSpdySession* session)
313 : QuicStream(pending, session, /*is_static=*/false),
314 session_(session),
315 adapter_(session, this, sequencer()),
316 needs_to_send_preamble_(false) {
317 sequencer()->set_level_triggered(true);
318 }
319
WebTransportHttp3UnidirectionalStream(QuicStreamId id,QuicSpdySession * session,WebTransportSessionId session_id)320 WebTransportHttp3UnidirectionalStream::WebTransportHttp3UnidirectionalStream(
321 QuicStreamId id, QuicSpdySession* session, WebTransportSessionId session_id)
322 : QuicStream(id, session, /*is_static=*/false, WRITE_UNIDIRECTIONAL),
323 session_(session),
324 adapter_(session, this, sequencer()),
325 session_id_(session_id),
326 needs_to_send_preamble_(true) {}
327
WritePreamble()328 void WebTransportHttp3UnidirectionalStream::WritePreamble() {
329 if (!needs_to_send_preamble_ || !session_id_.has_value()) {
330 QUIC_BUG(WebTransportHttp3UnidirectionalStream duplicate preamble)
331 << ENDPOINT << "Sending preamble on stream ID " << id()
332 << " at the wrong time.";
333 OnUnrecoverableError(QUIC_INTERNAL_ERROR,
334 "Attempting to send a WebTransport unidirectional "
335 "stream preamble at the wrong time.");
336 return;
337 }
338
339 QuicConnection::ScopedPacketFlusher flusher(session_->connection());
340 char buffer[sizeof(uint64_t) * 2]; // varint62, varint62
341 QuicDataWriter writer(sizeof(buffer), buffer);
342 bool success = true;
343 success = success && writer.WriteVarInt62(kWebTransportUnidirectionalStream);
344 success = success && writer.WriteVarInt62(*session_id_);
345 QUICHE_DCHECK(success);
346 WriteOrBufferData(absl::string_view(buffer, writer.length()), /*fin=*/false,
347 /*ack_listener=*/nullptr);
348 QUIC_DVLOG(1) << ENDPOINT << "Sent stream type and session ID ("
349 << *session_id_ << ") on WebTransport stream " << id();
350 needs_to_send_preamble_ = false;
351 }
352
ReadSessionId()353 bool WebTransportHttp3UnidirectionalStream::ReadSessionId() {
354 iovec iov;
355 if (!sequencer()->GetReadableRegion(&iov)) {
356 return false;
357 }
358 QuicDataReader reader(static_cast<const char*>(iov.iov_base), iov.iov_len);
359 WebTransportSessionId session_id;
360 uint8_t session_id_length = reader.PeekVarInt62Length();
361 if (!reader.ReadVarInt62(&session_id)) {
362 // If all of the data has been received, and we still cannot associate the
363 // stream with a session, consume all of the data so that the stream can
364 // be closed.
365 if (sequencer()->IsAllDataAvailable()) {
366 QUIC_DLOG(WARNING)
367 << ENDPOINT << "Failed to associate WebTransport stream " << id()
368 << " with a session because the stream ended prematurely.";
369 sequencer()->MarkConsumed(sequencer()->NumBytesBuffered());
370 }
371 return false;
372 }
373 sequencer()->MarkConsumed(session_id_length);
374 session_id_ = session_id;
375 session_->AssociateIncomingWebTransportStreamWithSession(session_id, id());
376 return true;
377 }
378
OnDataAvailable()379 void WebTransportHttp3UnidirectionalStream::OnDataAvailable() {
380 if (!session_id_.has_value()) {
381 if (!ReadSessionId()) {
382 return;
383 }
384 }
385
386 adapter_.OnDataAvailable();
387 }
388
OnCanWriteNewData()389 void WebTransportHttp3UnidirectionalStream::OnCanWriteNewData() {
390 adapter_.OnCanWriteNewData();
391 }
392
OnClose()393 void WebTransportHttp3UnidirectionalStream::OnClose() {
394 QuicStream::OnClose();
395
396 if (!session_id_.has_value()) {
397 return;
398 }
399 WebTransportHttp3* session = session_->GetWebTransportSession(*session_id_);
400 if (session == nullptr) {
401 QUIC_DLOG(WARNING) << ENDPOINT << "WebTransport stream " << id()
402 << " attempted to notify parent session " << *session_id_
403 << ", but the session could not be found.";
404 return;
405 }
406 session->OnStreamClosed(id());
407 }
408
OnStreamReset(const QuicRstStreamFrame & frame)409 void WebTransportHttp3UnidirectionalStream::OnStreamReset(
410 const QuicRstStreamFrame& frame) {
411 if (adapter_.visitor() != nullptr) {
412 adapter_.visitor()->OnResetStreamReceived(
413 Http3ErrorToWebTransportOrDefault(frame.ietf_error_code));
414 }
415 QuicStream::OnStreamReset(frame);
416 }
OnStopSending(QuicResetStreamError error)417 bool WebTransportHttp3UnidirectionalStream::OnStopSending(
418 QuicResetStreamError error) {
419 if (adapter_.visitor() != nullptr) {
420 adapter_.visitor()->OnStopSendingReceived(
421 Http3ErrorToWebTransportOrDefault(error.ietf_application_code()));
422 }
423 return QuicStream::OnStopSending(error);
424 }
OnWriteSideInDataRecvdState()425 void WebTransportHttp3UnidirectionalStream::OnWriteSideInDataRecvdState() {
426 if (adapter_.visitor() != nullptr) {
427 adapter_.visitor()->OnWriteSideInDataRecvdState();
428 }
429
430 QuicStream::OnWriteSideInDataRecvdState();
431 }
432
433 namespace {
434 constexpr uint64_t kWebTransportMappedErrorCodeFirst = 0x52e4a40fa8db;
435 constexpr uint64_t kWebTransportMappedErrorCodeLast = 0x52e5ac983162;
436 constexpr WebTransportStreamError kDefaultWebTransportError = 0;
437 } // namespace
438
Http3ErrorToWebTransport(uint64_t http3_error_code)439 std::optional<WebTransportStreamError> Http3ErrorToWebTransport(
440 uint64_t http3_error_code) {
441 // Ensure the code is within the valid range.
442 if (http3_error_code < kWebTransportMappedErrorCodeFirst ||
443 http3_error_code > kWebTransportMappedErrorCodeLast) {
444 return std::nullopt;
445 }
446 // Exclude GREASE codepoints.
447 if ((http3_error_code - 0x21) % 0x1f == 0) {
448 return std::nullopt;
449 }
450
451 uint64_t shifted = http3_error_code - kWebTransportMappedErrorCodeFirst;
452 uint64_t result = shifted - shifted / 0x1f;
453 QUICHE_DCHECK_LE(result,
454 std::numeric_limits<webtransport::StreamErrorCode>::max());
455 return static_cast<WebTransportStreamError>(result);
456 }
457
Http3ErrorToWebTransportOrDefault(uint64_t http3_error_code)458 WebTransportStreamError Http3ErrorToWebTransportOrDefault(
459 uint64_t http3_error_code) {
460 std::optional<WebTransportStreamError> result =
461 Http3ErrorToWebTransport(http3_error_code);
462 return result.has_value() ? *result : kDefaultWebTransportError;
463 }
464
WebTransportErrorToHttp3(WebTransportStreamError webtransport_error_code)465 uint64_t WebTransportErrorToHttp3(
466 WebTransportStreamError webtransport_error_code) {
467 return kWebTransportMappedErrorCodeFirst + webtransport_error_code +
468 webtransport_error_code / 0x1e;
469 }
470
471 } // namespace quic
472