xref: /aosp_15_r20/external/cronet/net/third_party/quiche/src/quiche/quic/core/http/web_transport_http3.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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