• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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