blob: 97bc56acaaeab695fc52b434148c2dd6e7547eb5 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/quic/dedicated_web_transport_http3_client.h"
#include "base/containers/contains.h"
#include "base/containers/cxx20_erase.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/task/single_thread_task_runner.h"
#include "net/base/address_list.h"
#include "net/base/port_util.h"
#include "net/base/url_util.h"
#include "net/http/http_network_session.h"
#include "net/log/net_log_values.h"
#include "net/proxy_resolution/configured_proxy_resolution_service.h"
#include "net/proxy_resolution/proxy_resolution_request.h"
#include "net/quic/address_utils.h"
#include "net/quic/crypto/proof_verifier_chromium.h"
#include "net/quic/quic_chromium_alarm_factory.h"
#include "net/spdy/spdy_http_utils.h"
#include "net/third_party/quiche/src/quiche/quic/core/http/web_transport_http3.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_connection.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_utils.h"
#include "net/url_request/url_request_context.h"
#include "url/scheme_host_port.h"
namespace net {
namespace {
// From
// https://wicg.github.io/web-transport/#dom-quictransportconfiguration-server_certificate_fingerprints
constexpr int kCustomCertificateMaxValidityDays = 14;
// The time the client would wait for the server to acknowledge the session
// being closed.
constexpr base::TimeDelta kMaxCloseTimeout = base::Seconds(2);
std::set<std::string> HostsFromOrigins(std::set<HostPortPair> origins) {
std::set<std::string> hosts;
for (const auto& origin : origins) {
hosts.insert(origin.host());
}
return hosts;
}
// A version of WebTransportFingerprintProofVerifier that enforces
// Chromium-specific policies.
class ChromiumWebTransportFingerprintProofVerifier
: public quic::WebTransportFingerprintProofVerifier {
public:
using WebTransportFingerprintProofVerifier::
WebTransportFingerprintProofVerifier;
protected:
bool IsKeyTypeAllowedByPolicy(
const quic::CertificateView& certificate) override {
if (certificate.public_key_type() == quic::PublicKeyType::kRsa) {
return false;
}
return WebTransportFingerprintProofVerifier::IsKeyTypeAllowedByPolicy(
certificate);
}
};
std::unique_ptr<quic::ProofVerifier> CreateProofVerifier(
const NetworkAnonymizationKey& anonymization_key,
URLRequestContext* context,
const WebTransportParameters& parameters) {
if (parameters.server_certificate_fingerprints.empty()) {
return std::make_unique<ProofVerifierChromium>(
context->cert_verifier(), context->ct_policy_enforcer(),
context->transport_security_state(), context->sct_auditing_delegate(),
HostsFromOrigins(
context->quic_context()->params()->origins_to_force_quic_on),
anonymization_key);
}
auto verifier =
std::make_unique<ChromiumWebTransportFingerprintProofVerifier>(
context->quic_context()->clock(), kCustomCertificateMaxValidityDays);
for (const quic::CertificateFingerprint& fingerprint :
parameters.server_certificate_fingerprints) {
bool success = verifier->AddFingerprint(fingerprint);
if (!success) {
DLOG(WARNING) << "Failed to add a certificate fingerprint: "
<< fingerprint.fingerprint;
}
}
return verifier;
}
void RecordNetLogQuicSessionClientStateChanged(
NetLogWithSource& net_log,
WebTransportState last_state,
WebTransportState next_state,
const absl::optional<WebTransportError>& error) {
net_log.AddEvent(
NetLogEventType::QUIC_SESSION_WEBTRANSPORT_CLIENT_STATE_CHANGED, [&] {
base::Value::Dict dict;
dict.Set("last_state", WebTransportStateString(last_state));
dict.Set("next_state", WebTransportStateString(next_state));
if (error.has_value()) {
base::Value::Dict error_dict;
error_dict.Set("net_error", error->net_error);
error_dict.Set("quic_error", error->quic_error);
error_dict.Set("details", error->details);
dict.Set("error", std::move(error_dict));
}
return dict;
});
}
// The stream associated with an extended CONNECT request for the WebTransport
// session.
class ConnectStream : public quic::QuicSpdyClientStream {
public:
ConnectStream(quic::QuicStreamId id,
quic::QuicSpdyClientSession* session,
quic::StreamType type,
DedicatedWebTransportHttp3Client* client)
: quic::QuicSpdyClientStream(id, session, type), client_(client) {}
void OnInitialHeadersComplete(
bool fin,
size_t frame_len,
const quic::QuicHeaderList& header_list) override {
quic::QuicSpdyClientStream::OnInitialHeadersComplete(fin, frame_len,
header_list);
client_->OnHeadersComplete(response_headers());
}
void OnClose() override {
quic::QuicSpdyClientStream::OnClose();
if (fin_received() && fin_sent()) {
// Clean close.
return;
}
if (stream_error() == quic::QUIC_STREAM_CONNECTION_ERROR) {
// If stream is closed due to the connection error, OnConnectionClosed()
// will populate the correct error details.
return;
}
client_->OnConnectStreamAborted();
}
void OnWriteSideInDataRecvdState() override {
quic::QuicSpdyClientStream::OnWriteSideInDataRecvdState();
client_->OnConnectStreamWriteSideInDataRecvdState();
}
private:
raw_ptr<DedicatedWebTransportHttp3Client> client_;
};
class DedicatedWebTransportHttp3ClientSession
: public quic::QuicSpdyClientSession {
public:
DedicatedWebTransportHttp3ClientSession(
const quic::QuicConfig& config,
const quic::ParsedQuicVersionVector& supported_versions,
quic::QuicConnection* connection,
const quic::QuicServerId& server_id,
quic::QuicCryptoClientConfig* crypto_config,
quic::QuicClientPushPromiseIndex* push_promise_index,
DedicatedWebTransportHttp3Client* client)
: quic::QuicSpdyClientSession(config,
supported_versions,
connection,
server_id,
crypto_config,
push_promise_index),
client_(client) {}
bool OnSettingsFrame(const quic::SettingsFrame& frame) override {
if (!quic::QuicSpdyClientSession::OnSettingsFrame(frame)) {
return false;
}
client_->OnSettingsReceived();
return true;
}
bool ShouldNegotiateWebTransport() override { return true; }
quic::HttpDatagramSupport LocalHttpDatagramSupport() override {
return quic::HttpDatagramSupport::kRfcAndDraft04;
}
void OnConnectionClosed(const quic::QuicConnectionCloseFrame& frame,
quic::ConnectionCloseSource source) override {
quic::QuicSpdyClientSession::OnConnectionClosed(frame, source);
client_->OnConnectionClosed(frame.quic_error_code, frame.error_details,
source);
}
ConnectStream* CreateConnectStream() {
if (!ShouldCreateOutgoingBidirectionalStream()) {
return nullptr;
}
std::unique_ptr<ConnectStream> stream =
std::make_unique<ConnectStream>(GetNextOutgoingBidirectionalStreamId(),
this, quic::BIDIRECTIONAL, client_);
ConnectStream* stream_ptr = stream.get();
ActivateStream(std::move(stream));
return stream_ptr;
}
void OnDatagramProcessed(
absl::optional<quic::MessageStatus> status) override {
client_->OnDatagramProcessed(
status.has_value() ? absl::optional<quic::MessageStatus>(*status)
: absl::optional<quic::MessageStatus>());
}
private:
raw_ptr<DedicatedWebTransportHttp3Client> client_;
};
class WebTransportVisitorProxy : public quic::WebTransportVisitor {
public:
explicit WebTransportVisitorProxy(quic::WebTransportVisitor* visitor)
: visitor_(visitor) {}
void OnSessionReady(const spdy::Http2HeaderBlock& block) override {
visitor_->OnSessionReady(block);
}
void OnSessionClosed(quic::WebTransportSessionError error_code,
const std::string& error_message) override {
visitor_->OnSessionClosed(error_code, error_message);
}
void OnIncomingBidirectionalStreamAvailable() override {
visitor_->OnIncomingBidirectionalStreamAvailable();
}
void OnIncomingUnidirectionalStreamAvailable() override {
visitor_->OnIncomingUnidirectionalStreamAvailable();
}
void OnDatagramReceived(absl::string_view datagram) override {
visitor_->OnDatagramReceived(datagram);
}
void OnCanCreateNewOutgoingBidirectionalStream() override {
visitor_->OnCanCreateNewOutgoingBidirectionalStream();
}
void OnCanCreateNewOutgoingUnidirectionalStream() override {
visitor_->OnCanCreateNewOutgoingUnidirectionalStream();
}
private:
raw_ptr<quic::WebTransportVisitor> visitor_;
};
bool IsTerminalState(WebTransportState state) {
return state == WebTransportState::CLOSED ||
state == WebTransportState::FAILED;
}
} // namespace
DedicatedWebTransportHttp3Client::DedicatedWebTransportHttp3Client(
const GURL& url,
const url::Origin& origin,
WebTransportClientVisitor* visitor,
const NetworkAnonymizationKey& anonymization_key,
URLRequestContext* context,
const WebTransportParameters& parameters)
: url_(url),
origin_(origin),
anonymization_key_(anonymization_key),
context_(context),
visitor_(visitor),
quic_context_(context->quic_context()),
net_log_(NetLogWithSource::Make(context->net_log(),
NetLogSourceType::WEB_TRANSPORT_CLIENT)),
task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault().get()),
alarm_factory_(
std::make_unique<QuicChromiumAlarmFactory>(task_runner_,
quic_context_->clock())),
// TODO(vasilvv): proof verifier should have proper error reporting
// (currently, all certificate verification errors result in "TLS
// handshake error" even when more detailed message is available). This
// requires implementing ProofHandler::OnProofVerifyDetailsAvailable.
crypto_config_(
CreateProofVerifier(anonymization_key_, context, parameters),
/* session_cache */ nullptr) {
net_log_.BeginEvent(
NetLogEventType::QUIC_SESSION_WEBTRANSPORT_CLIENT_ALIVE, [&] {
base::Value::Dict dict;
dict.Set("url", url.possibly_invalid_spec());
dict.Set("network_anonymization_key",
anonymization_key.ToDebugString());
return dict;
});
}
DedicatedWebTransportHttp3Client::~DedicatedWebTransportHttp3Client() {
net_log_.EndEventWithNetErrorCode(
NetLogEventType::QUIC_SESSION_WEBTRANSPORT_CLIENT_ALIVE,
error_ ? error_->net_error : OK);
// |session_| owns this, so we need to make sure we release it before
// it gets dangling.
connection_ = nullptr;
}
void DedicatedWebTransportHttp3Client::Connect() {
if (state_ != WebTransportState::NEW ||
next_connect_state_ != CONNECT_STATE_NONE) {
NOTREACHED();
return;
}
TransitionToState(WebTransportState::CONNECTING);
next_connect_state_ = CONNECT_STATE_INIT;
DoLoop(OK);
}
void DedicatedWebTransportHttp3Client::Close(
const absl::optional<WebTransportCloseInfo>& close_info) {
base::TimeDelta probe_timeout = base::Microseconds(
connection_->sent_packet_manager().GetPtoDelay().ToMicroseconds());
// Wait for at least three PTOs similar to what's used in
// https://www.rfc-editor.org/rfc/rfc9000.html#name-immediate-close
base::TimeDelta close_timeout = std::min(3 * probe_timeout, kMaxCloseTimeout);
close_timeout_timer_.Start(
FROM_HERE, close_timeout,
base::BindOnce(&DedicatedWebTransportHttp3Client::OnCloseTimeout,
weak_factory_.GetWeakPtr()));
if (close_info.has_value()) {
session()->CloseSession(close_info->code, close_info->reason);
} else {
session()->CloseSession(0, "");
}
}
quic::WebTransportSession* DedicatedWebTransportHttp3Client::session() {
if (web_transport_session_ == nullptr)
return nullptr;
return web_transport_session_;
}
void DedicatedWebTransportHttp3Client::DoLoop(int rv) {
do {
ConnectState connect_state = next_connect_state_;
next_connect_state_ = CONNECT_STATE_NONE;
switch (connect_state) {
case CONNECT_STATE_INIT:
DCHECK_EQ(rv, OK);
rv = DoInit();
break;
case CONNECT_STATE_CHECK_PROXY:
DCHECK_EQ(rv, OK);
rv = DoCheckProxy();
break;
case CONNECT_STATE_CHECK_PROXY_COMPLETE:
rv = DoCheckProxyComplete(rv);
break;
case CONNECT_STATE_RESOLVE_HOST:
DCHECK_EQ(rv, OK);
rv = DoResolveHost();
break;
case CONNECT_STATE_RESOLVE_HOST_COMPLETE:
rv = DoResolveHostComplete(rv);
break;
case CONNECT_STATE_CONNECT:
DCHECK_EQ(rv, OK);
rv = DoConnect();
break;
case CONNECT_STATE_CONNECT_CONFIGURE:
rv = DoConnectConfigure(rv);
break;
case CONNECT_STATE_CONNECT_COMPLETE:
rv = DoConnectComplete();
break;
case CONNECT_STATE_SEND_REQUEST:
DCHECK_EQ(rv, OK);
rv = DoSendRequest();
break;
case CONNECT_STATE_CONFIRM_CONNECTION:
DCHECK_EQ(rv, OK);
rv = DoConfirmConnection();
break;
default:
NOTREACHED() << "Invalid state reached: " << connect_state;
rv = ERR_FAILED;
break;
}
} while (rv == OK && next_connect_state_ != CONNECT_STATE_NONE);
if (rv == OK || rv == ERR_IO_PENDING)
return;
SetErrorIfNecessary(rv);
TransitionToState(WebTransportState::FAILED);
}
int DedicatedWebTransportHttp3Client::DoInit() {
if (!url_.is_valid())
return ERR_INVALID_URL;
if (url_.scheme_piece() != url::kHttpsScheme)
return ERR_DISALLOWED_URL_SCHEME;
if (!IsPortAllowedForScheme(url_.EffectiveIntPort(), url_.scheme_piece()))
return ERR_UNSAFE_PORT;
// TODO(vasilvv): check if QUIC is disabled by policy.
// Ensure that RFC 9000 is always supported.
supported_versions_ = quic::ParsedQuicVersionVector{
quic::ParsedQuicVersion::RFCv1(),
};
// Add other supported versions if available.
for (quic::ParsedQuicVersion& version :
quic_context_->params()->supported_versions) {
if (base::Contains(supported_versions_, version))
continue; // Skip as we've already added it above.
supported_versions_.push_back(version);
}
if (supported_versions_.empty()) {
DLOG(ERROR) << "Attempted using WebTransport with no compatible QUIC "
"versions available";
return ERR_NOT_IMPLEMENTED;
}
next_connect_state_ = CONNECT_STATE_CHECK_PROXY;
return OK;
}
int DedicatedWebTransportHttp3Client::DoCheckProxy() {
next_connect_state_ = CONNECT_STATE_CHECK_PROXY_COMPLETE;
return context_->proxy_resolution_service()->ResolveProxy(
url_, /* method */ "CONNECT", anonymization_key_, &proxy_info_,
base::BindOnce(&DedicatedWebTransportHttp3Client::DoLoop,
base::Unretained(this)),
&proxy_resolution_request_, net_log_);
}
int DedicatedWebTransportHttp3Client::DoCheckProxyComplete(int rv) {
if (rv != OK)
return rv;
// If a proxy is configured, we fail the connection.
if (!proxy_info_.is_direct())
return ERR_TUNNEL_CONNECTION_FAILED;
next_connect_state_ = CONNECT_STATE_RESOLVE_HOST;
return OK;
}
int DedicatedWebTransportHttp3Client::DoResolveHost() {
next_connect_state_ = CONNECT_STATE_RESOLVE_HOST_COMPLETE;
HostResolver::ResolveHostParameters parameters;
resolve_host_request_ = context_->host_resolver()->CreateRequest(
url::SchemeHostPort(url_), anonymization_key_, net_log_, absl::nullopt);
return resolve_host_request_->Start(base::BindOnce(
&DedicatedWebTransportHttp3Client::DoLoop, base::Unretained(this)));
}
int DedicatedWebTransportHttp3Client::DoResolveHostComplete(int rv) {
if (rv != OK)
return rv;
DCHECK(resolve_host_request_->GetAddressResults());
next_connect_state_ = CONNECT_STATE_CONNECT;
return OK;
}
int DedicatedWebTransportHttp3Client::DoConnect() {
next_connect_state_ = CONNECT_STATE_CONNECT_CONFIGURE;
// TODO(vasilvv): consider unifying parts of this code with QuicSocketFactory
// (which currently has a lot of code specific to QuicChromiumClientSession).
socket_ = context_->GetNetworkSessionContext()
->client_socket_factory->CreateDatagramClientSocket(
DatagramSocket::DEFAULT_BIND, net_log_.net_log(),
net_log_.source());
if (quic_context_->params()->enable_socket_recv_optimization)
socket_->EnableRecvOptimization();
socket_->UseNonBlockingIO();
IPEndPoint server_address =
*resolve_host_request_->GetAddressResults()->begin();
return socket_->ConnectAsync(
server_address, base::BindOnce(&DedicatedWebTransportHttp3Client::DoLoop,
base::Unretained(this)));
}
void DedicatedWebTransportHttp3Client::CreateConnection() {
// Delete the objects in the same order they would be normally deleted by the
// destructor.
packet_reader_ = nullptr;
session_ = nullptr;
IPEndPoint server_address =
*resolve_host_request_->GetAddressResults()->begin();
quic::QuicConnectionId connection_id =
quic::QuicUtils::CreateRandomConnectionId(
quic_context_->random_generator());
auto connection = std::make_unique<quic::QuicConnection>(
connection_id, quic::QuicSocketAddress(),
ToQuicSocketAddress(server_address), quic_context_->helper(),
alarm_factory_.get(),
new QuicChromiumPacketWriter(socket_.get(), task_runner_),
/* owns_writer */ true, quic::Perspective::IS_CLIENT, supported_versions_,
connection_id_generator_);
connection_ = connection.get();
connection->SetMaxPacketLength(quic_context_->params()->max_packet_length);
session_ = std::make_unique<DedicatedWebTransportHttp3ClientSession>(
InitializeQuicConfig(*quic_context_->params()), supported_versions_,
connection.release(),
quic::QuicServerId(url_.host(), url_.EffectiveIntPort()), &crypto_config_,
&push_promise_index_, this);
if (!original_supported_versions_.empty()) {
session_->set_client_original_supported_versions(
original_supported_versions_);
}
packet_reader_ = std::make_unique<QuicChromiumPacketReader>(
socket_.get(), quic_context_->clock(), this, kQuicYieldAfterPacketsRead,
quic::QuicTime::Delta::FromMilliseconds(
kQuicYieldAfterDurationMilliseconds),
net_log_);
event_logger_ = std::make_unique<QuicEventLogger>(session_.get(), net_log_);
connection_->set_debug_visitor(event_logger_.get());
connection_->set_creator_debug_delegate(event_logger_.get());
session_->Initialize();
packet_reader_->StartReading();
DCHECK(session_->WillNegotiateWebTransport());
session_->CryptoConnect();
}
int DedicatedWebTransportHttp3Client::DoConnectComplete() {
if (!connection_->connected()) {
return ERR_QUIC_PROTOCOL_ERROR;
}
// Fail the connection if the received SETTINGS do not support WebTransport.
if (!session_->SupportsWebTransport()) {
return ERR_METHOD_NOT_SUPPORTED;
}
safe_to_report_error_details_ = true;
next_connect_state_ = CONNECT_STATE_SEND_REQUEST;
return OK;
}
int DedicatedWebTransportHttp3Client::DoConnectConfigure(int rv) {
if (rv != OK) {
return rv;
}
rv = socket_->SetReceiveBufferSize(kQuicSocketReceiveBufferSize);
if (rv != OK) {
return rv;
}
rv = socket_->SetDoNotFragment();
if (rv == ERR_NOT_IMPLEMENTED) {
rv = OK;
}
if (rv != OK) {
return rv;
}
rv = socket_->SetSendBufferSize(quic::kMaxOutgoingPacketSize * 20);
if (rv != OK) {
return rv;
}
next_connect_state_ = CONNECT_STATE_CONNECT_COMPLETE;
CreateConnection();
return ERR_IO_PENDING;
}
void DedicatedWebTransportHttp3Client::OnSettingsReceived() {
DCHECK_EQ(next_connect_state_, CONNECT_STATE_CONNECT_COMPLETE);
// Wait until the SETTINGS parser is finished, and then send the request.
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&DedicatedWebTransportHttp3Client::DoLoop,
weak_factory_.GetWeakPtr(), OK));
}
void DedicatedWebTransportHttp3Client::OnHeadersComplete(
const spdy::Http2HeaderBlock& headers) {
http_response_info_ = std::make_unique<HttpResponseInfo>();
const int rv = SpdyHeadersToHttpResponse(headers, http_response_info_.get());
if (rv != OK) {
SetErrorIfNecessary(ERR_QUIC_PROTOCOL_ERROR);
TransitionToState(WebTransportState::FAILED);
return;
}
// TODO(vasilvv): add support for this header in downstream tests and remove
// this.
DCHECK(http_response_info_->headers);
http_response_info_->headers->RemoveHeader("sec-webtransport-http3-draft");
DCHECK_EQ(next_connect_state_, CONNECT_STATE_CONFIRM_CONNECTION);
DoLoop(OK);
}
void DedicatedWebTransportHttp3Client::
OnConnectStreamWriteSideInDataRecvdState() {
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&DedicatedWebTransportHttp3Client::TransitionToState,
weak_factory_.GetWeakPtr(), WebTransportState::CLOSED));
}
void DedicatedWebTransportHttp3Client::OnConnectStreamAborted() {
SetErrorIfNecessary(session_ready_ ? ERR_FAILED : ERR_METHOD_NOT_SUPPORTED);
TransitionToState(WebTransportState::FAILED);
}
void DedicatedWebTransportHttp3Client::OnCloseTimeout() {
SetErrorIfNecessary(ERR_TIMED_OUT);
TransitionToState(WebTransportState::FAILED);
}
int DedicatedWebTransportHttp3Client::DoSendRequest() {
quic::QuicConnection::ScopedPacketFlusher scope(connection_);
DedicatedWebTransportHttp3ClientSession* session =
static_cast<DedicatedWebTransportHttp3ClientSession*>(session_.get());
ConnectStream* stream = session->CreateConnectStream();
if (stream == nullptr) {
return ERR_QUIC_PROTOCOL_ERROR;
}
connect_stream_ = stream;
spdy::Http2HeaderBlock headers;
DCHECK_EQ(url_.scheme(), url::kHttpsScheme);
headers[":scheme"] = url_.scheme();
headers[":method"] = "CONNECT";
headers[":authority"] = GetHostAndOptionalPort(url_);
headers[":path"] = url_.PathForRequest();
headers[":protocol"] = "webtransport";
headers["sec-webtransport-http3-draft02"] = "1";
headers["origin"] = origin_.Serialize();
stream->WriteHeaders(std::move(headers), /*fin=*/false, nullptr);
web_transport_session_ = stream->web_transport();
if (web_transport_session_ == nullptr) {
return ERR_METHOD_NOT_SUPPORTED;
}
stream->web_transport()->SetVisitor(
std::make_unique<WebTransportVisitorProxy>(this));
next_connect_state_ = CONNECT_STATE_CONFIRM_CONNECTION;
return ERR_IO_PENDING;
}
int DedicatedWebTransportHttp3Client::DoConfirmConnection() {
if (!session_ready_) {
return ERR_METHOD_NOT_SUPPORTED;
}
TransitionToState(WebTransportState::CONNECTED);
return OK;
}
void DedicatedWebTransportHttp3Client::TransitionToState(
WebTransportState next_state) {
// Ignore all state transition requests if we have reached the terminal
// state.
if (IsTerminalState(state_)) {
DCHECK(IsTerminalState(next_state))
<< "from: " << state_ << ", to: " << next_state;
return;
}
DCHECK_NE(state_, next_state);
const WebTransportState last_state = state_;
state_ = next_state;
RecordNetLogQuicSessionClientStateChanged(net_log_, last_state, next_state,
error_);
switch (next_state) {
case WebTransportState::CONNECTING:
DCHECK_EQ(last_state, WebTransportState::NEW);
break;
case WebTransportState::CONNECTED:
DCHECK_EQ(last_state, WebTransportState::CONNECTING);
visitor_->OnConnected(http_response_info_->headers);
break;
case WebTransportState::CLOSED:
DCHECK_EQ(last_state, WebTransportState::CONNECTED);
connection_->CloseConnection(quic::QUIC_NO_ERROR,
"WebTransport client terminated",
quic::ConnectionCloseBehavior::SILENT_CLOSE);
visitor_->OnClosed(close_info_);
break;
case WebTransportState::FAILED:
DCHECK(error_.has_value());
if (last_state == WebTransportState::CONNECTING) {
visitor_->OnConnectionFailed(*error_);
break;
}
DCHECK_EQ(last_state, WebTransportState::CONNECTED);
// Ensure the connection is properly closed before deleting it.
connection_->CloseConnection(
quic::QUIC_INTERNAL_ERROR,
"WebTransportState::ERROR reached but the connection still open",
quic::ConnectionCloseBehavior::SILENT_CLOSE);
visitor_->OnError(*error_);
break;
default:
NOTREACHED() << "Invalid state reached: " << next_state;
break;
}
}
void DedicatedWebTransportHttp3Client::SetErrorIfNecessary(int error) {
SetErrorIfNecessary(error, quic::QUIC_NO_ERROR, ErrorToString(error));
}
void DedicatedWebTransportHttp3Client::SetErrorIfNecessary(
int error,
quic::QuicErrorCode quic_error,
base::StringPiece details) {
if (!error_) {
error_ = WebTransportError(error, quic_error, details,
safe_to_report_error_details_);
}
}
void DedicatedWebTransportHttp3Client::OnSessionReady(
const spdy::Http2HeaderBlock& /*spdy_headers*/) {
session_ready_ = true;
}
void DedicatedWebTransportHttp3Client::OnSessionClosed(
quic::WebTransportSessionError error_code,
const std::string& error_message) {
close_info_ = WebTransportCloseInfo(error_code, error_message);
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&DedicatedWebTransportHttp3Client::TransitionToState,
weak_factory_.GetWeakPtr(), WebTransportState::CLOSED));
}
void DedicatedWebTransportHttp3Client::
OnIncomingBidirectionalStreamAvailable() {
visitor_->OnIncomingBidirectionalStreamAvailable();
}
void DedicatedWebTransportHttp3Client::
OnIncomingUnidirectionalStreamAvailable() {
visitor_->OnIncomingUnidirectionalStreamAvailable();
}
void DedicatedWebTransportHttp3Client::OnDatagramReceived(
absl::string_view datagram) {
visitor_->OnDatagramReceived(datagram);
}
void DedicatedWebTransportHttp3Client::
OnCanCreateNewOutgoingBidirectionalStream() {
visitor_->OnCanCreateNewOutgoingBidirectionalStream();
}
void DedicatedWebTransportHttp3Client::
OnCanCreateNewOutgoingUnidirectionalStream() {
visitor_->OnCanCreateNewOutgoingUnidirectionalStream();
}
bool DedicatedWebTransportHttp3Client::OnReadError(
int result,
const DatagramClientSocket* socket) {
SetErrorIfNecessary(result);
connection_->CloseConnection(quic::QUIC_PACKET_READ_ERROR,
ErrorToString(result),
quic::ConnectionCloseBehavior::SILENT_CLOSE);
return false;
}
bool DedicatedWebTransportHttp3Client::OnPacket(
const quic::QuicReceivedPacket& packet,
const quic::QuicSocketAddress& local_address,
const quic::QuicSocketAddress& peer_address) {
session_->ProcessUdpPacket(local_address, peer_address, packet);
return connection_->connected();
}
int DedicatedWebTransportHttp3Client::HandleWriteError(
int error_code,
scoped_refptr<QuicChromiumPacketWriter::ReusableIOBuffer> /*last_packet*/) {
return error_code;
}
void DedicatedWebTransportHttp3Client::OnWriteError(int error_code) {
SetErrorIfNecessary(error_code);
connection_->OnWriteError(error_code);
}
void DedicatedWebTransportHttp3Client::OnWriteUnblocked() {
connection_->OnCanWrite();
}
void DedicatedWebTransportHttp3Client::OnConnectionClosed(
quic::QuicErrorCode error,
const std::string& error_details,
quic::ConnectionCloseSource source) {
// If the session is already in a terminal state due to reasons other than
// connection close, we should ignore it; otherwise we risk re-entering the
// connection teardown process.
if (IsTerminalState(state_)) {
return;
}
if (!retried_with_new_version_ &&
session_->error() == quic::QUIC_INVALID_VERSION) {
retried_with_new_version_ = true;
DCHECK(original_supported_versions_.empty());
original_supported_versions_ = supported_versions_;
base::EraseIf(
supported_versions_, [this](const quic::ParsedQuicVersion& version) {
return !base::Contains(
session_->connection()->server_supported_versions(), version);
});
if (!supported_versions_.empty()) {
// Since this is a callback from QuicConnection, we can't replace the
// connection object in this method; do it from the top of the event loop
// instead.
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&DedicatedWebTransportHttp3Client::CreateConnection,
weak_factory_.GetWeakPtr()));
return;
}
// If there are no supported versions, treat this as a regular error.
}
if (error == quic::QUIC_NO_ERROR) {
TransitionToState(WebTransportState::CLOSED);
return;
}
SetErrorIfNecessary(ERR_QUIC_PROTOCOL_ERROR, error, error_details);
if (state_ == WebTransportState::CONNECTING) {
DoLoop(OK);
return;
}
TransitionToState(WebTransportState::FAILED);
}
void DedicatedWebTransportHttp3Client::OnDatagramProcessed(
absl::optional<quic::MessageStatus> status) {
visitor_->OnDatagramProcessed(status);
}
} // namespace net