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