// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/http/http_proxy_client_socket_wrapper.h"

#include <utility>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback_helpers.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_macros.h"
#include "base/values.h"
#include "net/http/http_proxy_client_socket.h"
#include "net/http/http_response_info.h"
#include "net/log/net_log_event_type.h"
#include "net/log/net_log_source.h"
#include "net/log/net_log_source_type.h"
#include "net/quic/quic_http_utils.h"
#include "net/quic/quic_proxy_client_socket.h"
#include "net/socket/client_socket_factory.h"
#include "net/socket/client_socket_handle.h"
#include "net/socket/socket_tag.h"
#include "net/spdy/spdy_proxy_client_socket.h"
#include "net/spdy/spdy_session.h"
#include "net/spdy/spdy_session_pool.h"
#include "net/spdy/spdy_stream.h"
#include "net/ssl/ssl_cert_request_info.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "url/gurl.h"

namespace net {

HttpProxyClientSocketWrapper::HttpProxyClientSocketWrapper(
    const std::string& group_name,
    RequestPriority priority,
    const SocketTag& socket_tag,
    ClientSocketPool::RespectLimits respect_limits,
    base::TimeDelta connect_timeout_duration,
    base::TimeDelta proxy_negotiation_timeout_duration,
    TransportClientSocketPool* transport_pool,
    SSLClientSocketPool* ssl_pool,
    const scoped_refptr<TransportSocketParams>& transport_params,
    const scoped_refptr<SSLSocketParams>& ssl_params,
    quic::QuicTransportVersion quic_version,
    const std::string& user_agent,
    const HostPortPair& endpoint,
    HttpAuthCache* http_auth_cache,
    HttpAuthHandlerFactory* http_auth_handler_factory,
    SpdySessionPool* spdy_session_pool,
    QuicStreamFactory* quic_stream_factory,
    bool is_trusted_proxy,
    bool tunnel,
    const NetworkTrafficAnnotationTag& traffic_annotation,
    const NetLogWithSource& net_log)
    : next_state_(STATE_NONE),
      group_name_(group_name),
      priority_(priority),
      initial_socket_tag_(socket_tag),
      respect_limits_(respect_limits),
      connect_timeout_duration_(connect_timeout_duration),
      proxy_negotiation_timeout_duration_(proxy_negotiation_timeout_duration),
      transport_pool_(transport_pool),
      ssl_pool_(ssl_pool),
      transport_params_(transport_params),
      ssl_params_(ssl_params),
      quic_version_(quic_version),
      user_agent_(user_agent),
      endpoint_(endpoint),
      spdy_session_pool_(spdy_session_pool),
      has_restarted_(false),
      tunnel_(tunnel),
      using_spdy_(false),
      is_trusted_proxy_(is_trusted_proxy),
      quic_stream_request_(quic_stream_factory),
      http_auth_controller_(
          tunnel ? new HttpAuthController(
                       HttpAuth::AUTH_PROXY,
                       GURL((ssl_params_.get() ? "https://" : "http://") +
                            GetDestination().host_port_pair().ToString()),
                       http_auth_cache,
                       http_auth_handler_factory)
                 : nullptr),
      net_log_(NetLogWithSource::Make(
          net_log.net_log(),
          NetLogSourceType::PROXY_CLIENT_SOCKET_WRAPPER)),
      traffic_annotation_(traffic_annotation) {
  net_log_.BeginEvent(NetLogEventType::SOCKET_ALIVE,
                      net_log.source().ToEventParametersCallback());
  // If doing a QUIC proxy, |quic_version| must not be
  // quic::QUIC_VERSION_UNSUPPORTED, and |ssl_params| must be valid while
  // |transport_params| is null. Otherwise, |quic_version| must be
  // quic::QUIC_VERSION_UNSUPPORTED, and exactly one of |transport_params| or
  // |ssl_params| must be set.
  DCHECK(quic_version_ == quic::QUIC_VERSION_UNSUPPORTED
             ? (bool)transport_params != (bool)ssl_params
             : !transport_params && ssl_params);
}

HttpProxyClientSocketWrapper::~HttpProxyClientSocketWrapper() {
  // Make sure no sockets are returned to the lower level socket pools.
  Disconnect();

  net_log_.EndEvent(NetLogEventType::SOCKET_ALIVE);
}

LoadState HttpProxyClientSocketWrapper::GetConnectLoadState() const {
  switch (next_state_) {
    case STATE_BEGIN_CONNECT:
    case STATE_TCP_CONNECT:
    case STATE_TCP_CONNECT_COMPLETE:
    case STATE_SSL_CONNECT:
    case STATE_SSL_CONNECT_COMPLETE:
      return transport_socket_handle_->GetLoadState();
    case STATE_HTTP_PROXY_CONNECT:
    case STATE_HTTP_PROXY_CONNECT_COMPLETE:
    case STATE_SPDY_PROXY_CREATE_STREAM:
    case STATE_SPDY_PROXY_CREATE_STREAM_COMPLETE:
    case STATE_QUIC_PROXY_CREATE_SESSION:
    case STATE_QUIC_PROXY_CREATE_STREAM:
    case STATE_QUIC_PROXY_CREATE_STREAM_COMPLETE:
    case STATE_RESTART_WITH_AUTH:
    case STATE_RESTART_WITH_AUTH_COMPLETE:
      return LOAD_STATE_ESTABLISHING_PROXY_TUNNEL;
    case STATE_NONE:
      // May be possible for this method to be called after an error, shouldn't
      // be called after a successful connect.
      break;
  }
  return LOAD_STATE_IDLE;
}

std::unique_ptr<HttpResponseInfo>
HttpProxyClientSocketWrapper::GetAdditionalErrorState() {
  return std::move(error_response_info_);
}

const HttpResponseInfo* HttpProxyClientSocketWrapper::GetConnectResponseInfo()
    const {
  if (transport_socket_)
    return transport_socket_->GetConnectResponseInfo();
  return nullptr;
}

std::unique_ptr<HttpStream>
HttpProxyClientSocketWrapper::CreateConnectResponseStream() {
  if (transport_socket_)
    return transport_socket_->CreateConnectResponseStream();
  return nullptr;
}

int HttpProxyClientSocketWrapper::RestartWithAuth(
    CompletionOnceCallback callback) {
  DCHECK(!callback.is_null());
  DCHECK(connect_callback_.is_null());
  DCHECK(transport_socket_);
  DCHECK_EQ(STATE_NONE, next_state_);

  connect_callback_ = std::move(callback);
  next_state_ = STATE_RESTART_WITH_AUTH;
  return DoLoop(OK);
}

const scoped_refptr<HttpAuthController>&
HttpProxyClientSocketWrapper::GetAuthController() const {
  return http_auth_controller_;
}

bool HttpProxyClientSocketWrapper::IsUsingSpdy() const {
  if (transport_socket_)
    return transport_socket_->IsUsingSpdy();
  return false;
}

NextProto HttpProxyClientSocketWrapper::GetProxyNegotiatedProtocol() const {
  if (transport_socket_)
    return transport_socket_->GetProxyNegotiatedProtocol();
  return kProtoUnknown;
}

int HttpProxyClientSocketWrapper::Connect(CompletionOnceCallback callback) {
  DCHECK(!callback.is_null());
  DCHECK(connect_callback_.is_null());

  // If connecting or previously connected and not disconnected, return OK, to
  // match TCPClientSocket's behavior.
  if (next_state_ != STATE_NONE || transport_socket_)
    return OK;

  next_state_ = STATE_BEGIN_CONNECT;
  int rv = DoLoop(OK);
  if (rv == ERR_IO_PENDING) {
    connect_callback_ = std::move(callback);
  } else {
    connect_timer_.Stop();
  }

  return rv;
}

void HttpProxyClientSocketWrapper::Disconnect() {
  connect_callback_.Reset();
  connect_timer_.Stop();
  next_state_ = STATE_NONE;
  spdy_stream_request_.CancelRequest();
  if (transport_socket_handle_) {
    if (transport_socket_handle_->socket())
      transport_socket_handle_->socket()->Disconnect();
    transport_socket_handle_->Reset();
    transport_socket_handle_.reset();
  }

  if (transport_socket_)
    transport_socket_->Disconnect();
}

bool HttpProxyClientSocketWrapper::IsConnected() const {
  if (transport_socket_)
    return transport_socket_->IsConnected();
  // Don't return true if still connecting.  Shouldn't really matter, either
  // way.
  return false;
}

bool HttpProxyClientSocketWrapper::IsConnectedAndIdle() const {
  if (transport_socket_)
    return transport_socket_->IsConnectedAndIdle();
  return false;
}

const NetLogWithSource& HttpProxyClientSocketWrapper::NetLog() const {
  return net_log_;
}

bool HttpProxyClientSocketWrapper::WasEverUsed() const {
  // TODO(mmenke):  This is a little weird.  Figure out if something else should
  // be done.
  if (transport_socket_)
    return transport_socket_->WasEverUsed();
  return false;
}

bool HttpProxyClientSocketWrapper::WasAlpnNegotiated() const {
  if (transport_socket_)
    return transport_socket_->WasAlpnNegotiated();
  return false;
}

NextProto HttpProxyClientSocketWrapper::GetNegotiatedProtocol() const {
  if (transport_socket_)
    return transport_socket_->GetNegotiatedProtocol();
  return kProtoUnknown;
}

bool HttpProxyClientSocketWrapper::GetSSLInfo(SSLInfo* ssl_info) {
  if (transport_socket_)
    return transport_socket_->GetSSLInfo(ssl_info);
  return false;
}

void HttpProxyClientSocketWrapper::GetConnectionAttempts(
    ConnectionAttempts* out) const {
  // TODO(mmenke): Not clear how reconnecting for auth fits into things.
  if (transport_socket_) {
    transport_socket_->GetConnectionAttempts(out);
  } else {
    out->clear();
  }
}

void HttpProxyClientSocketWrapper::ClearConnectionAttempts() {
  if (transport_socket_)
    transport_socket_->ClearConnectionAttempts();
}

void HttpProxyClientSocketWrapper::AddConnectionAttempts(
    const ConnectionAttempts& attempts) {
  if (transport_socket_)
    transport_socket_->AddConnectionAttempts(attempts);
}

int64_t HttpProxyClientSocketWrapper::GetTotalReceivedBytes() const {
  return transport_socket_->GetTotalReceivedBytes();
}

void HttpProxyClientSocketWrapper::ApplySocketTag(const SocketTag& tag) {
  // HttpProxyClientSocketPool only tags once connected, when transport_socket_
  // is set. Socket tagging is not supported with tunneling. Socket tagging is
  // also not supported with proxy auth so ApplySocketTag() won't be called with
  // a specific (non-default) tag when transport_socket_ is cleared by
  // RestartWithAuth().
  if (tunnel_ || !transport_socket_) {
    CHECK(tag == SocketTag());
  } else {
    transport_socket_->ApplySocketTag(tag);
  }
}

int HttpProxyClientSocketWrapper::Read(IOBuffer* buf,
                                       int buf_len,
                                       CompletionOnceCallback callback) {
  if (transport_socket_)
    return transport_socket_->Read(buf, buf_len, std::move(callback));
  return ERR_SOCKET_NOT_CONNECTED;
}

int HttpProxyClientSocketWrapper::ReadIfReady(IOBuffer* buf,
                                              int buf_len,
                                              CompletionOnceCallback callback) {
  if (transport_socket_)
    return transport_socket_->ReadIfReady(buf, buf_len, std::move(callback));
  return ERR_SOCKET_NOT_CONNECTED;
}

int HttpProxyClientSocketWrapper::CancelReadIfReady() {
  if (transport_socket_)
    return transport_socket_->CancelReadIfReady();
  return OK;
}

int HttpProxyClientSocketWrapper::Write(
    IOBuffer* buf,
    int buf_len,
    CompletionOnceCallback callback,
    const NetworkTrafficAnnotationTag& traffic_annotation) {
  if (transport_socket_) {
    return transport_socket_->Write(buf, buf_len, std::move(callback),
                                    traffic_annotation);
  }
  return ERR_SOCKET_NOT_CONNECTED;
}

int HttpProxyClientSocketWrapper::SetReceiveBufferSize(int32_t size) {
  // TODO(mmenke):  Should this persist across reconnects?  Seems a little
  //     weird, and not done for normal reconnects.
  if (transport_socket_)
    return transport_socket_->SetReceiveBufferSize(size);
  return ERR_SOCKET_NOT_CONNECTED;
}

int HttpProxyClientSocketWrapper::SetSendBufferSize(int32_t size) {
  if (transport_socket_)
    return transport_socket_->SetSendBufferSize(size);
  return ERR_SOCKET_NOT_CONNECTED;
}

int HttpProxyClientSocketWrapper::GetPeerAddress(IPEndPoint* address) const {
  if (transport_socket_)
    return transport_socket_->GetPeerAddress(address);
  return ERR_SOCKET_NOT_CONNECTED;
}

int HttpProxyClientSocketWrapper::GetLocalAddress(IPEndPoint* address) const {
  if (transport_socket_)
    return transport_socket_->GetLocalAddress(address);
  return ERR_SOCKET_NOT_CONNECTED;
}

void HttpProxyClientSocketWrapper::OnIOComplete(int result) {
  int rv = DoLoop(result);
  if (rv != ERR_IO_PENDING) {
    connect_timer_.Stop();
    // May delete |this|.
    std::move(connect_callback_).Run(rv);
  }
}

int HttpProxyClientSocketWrapper::DoLoop(int result) {
  DCHECK_NE(next_state_, STATE_NONE);

  int rv = result;
  do {
    State state = next_state_;
    next_state_ = STATE_NONE;
    switch (state) {
      case STATE_BEGIN_CONNECT:
        DCHECK_EQ(OK, rv);
        rv = DoBeginConnect();
        break;
      case STATE_TCP_CONNECT:
        DCHECK_EQ(OK, rv);
        rv = DoTransportConnect();
        break;
      case STATE_TCP_CONNECT_COMPLETE:
        rv = DoTransportConnectComplete(rv);
        break;
      case STATE_SSL_CONNECT:
        DCHECK_EQ(OK, rv);
        rv = DoSSLConnect();
        break;
      case STATE_SSL_CONNECT_COMPLETE:
        rv = DoSSLConnectComplete(rv);
        break;
      case STATE_HTTP_PROXY_CONNECT:
        DCHECK_EQ(OK, rv);
        rv = DoHttpProxyConnect();
        break;
      case STATE_HTTP_PROXY_CONNECT_COMPLETE:
        rv = DoHttpProxyConnectComplete(rv);
        break;
      case STATE_SPDY_PROXY_CREATE_STREAM:
        DCHECK_EQ(OK, rv);
        rv = DoSpdyProxyCreateStream();
        break;
      case STATE_SPDY_PROXY_CREATE_STREAM_COMPLETE:
        rv = DoSpdyProxyCreateStreamComplete(rv);
        break;
      case STATE_QUIC_PROXY_CREATE_SESSION:
        DCHECK_EQ(OK, rv);
        rv = DoQuicProxyCreateSession();
        break;
      case STATE_QUIC_PROXY_CREATE_STREAM:
        rv = DoQuicProxyCreateStream(rv);
        break;
      case STATE_QUIC_PROXY_CREATE_STREAM_COMPLETE:
        rv = DoQuicProxyCreateStreamComplete(rv);
        break;
      case STATE_RESTART_WITH_AUTH:
        DCHECK_EQ(OK, rv);
        rv = DoRestartWithAuth();
        break;
      case STATE_RESTART_WITH_AUTH_COMPLETE:
        rv = DoRestartWithAuthComplete(rv);
        break;
      default:
        NOTREACHED() << "bad state";
        rv = ERR_FAILED;
        break;
    }
  } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);

  return rv;
}

int HttpProxyClientSocketWrapper::DoBeginConnect() {
  connect_start_time_ = base::TimeTicks::Now();
  SetConnectTimer(connect_timeout_duration_);
  if (quic_version_ != quic::QUIC_VERSION_UNSUPPORTED) {
    next_state_ = STATE_QUIC_PROXY_CREATE_SESSION;
  } else if (transport_params_) {
    next_state_ = STATE_TCP_CONNECT;
  } else {
    next_state_ = STATE_SSL_CONNECT;
  }
  return OK;
}

int HttpProxyClientSocketWrapper::DoTransportConnect() {
  next_state_ = STATE_TCP_CONNECT_COMPLETE;
  transport_socket_handle_.reset(new ClientSocketHandle());
  return transport_socket_handle_->Init(
      group_name_, transport_params_, priority_, initial_socket_tag_,
      respect_limits_,
      base::Bind(&HttpProxyClientSocketWrapper::OnIOComplete,
                 base::Unretained(this)),
      transport_pool_, net_log_);
}

int HttpProxyClientSocketWrapper::DoTransportConnectComplete(int result) {
  if (result != OK) {
    UMA_HISTOGRAM_MEDIUM_TIMES("Net.HttpProxy.ConnectLatency.Insecure.Error",
                               base::TimeTicks::Now() - connect_start_time_);
    return ERR_PROXY_CONNECTION_FAILED;
  }

  // Reset the timer to just the length of time allowed for HttpProxy handshake
  // so that a fast TCP connection plus a slow HttpProxy failure doesn't take
  // longer to timeout than it should.
  SetConnectTimer(proxy_negotiation_timeout_duration_);

  next_state_ = STATE_HTTP_PROXY_CONNECT;
  return result;
}

int HttpProxyClientSocketWrapper::DoSSLConnect() {
  DCHECK(ssl_params_);
  if (tunnel_) {
    SpdySessionKey key(ssl_params_->GetDirectConnectionParams()
                           ->destination()
                           .host_port_pair(),
                       ProxyServer::Direct(), PRIVACY_MODE_DISABLED,
                       initial_socket_tag_);
    if (spdy_session_pool_->FindAvailableSession(
            key, /* enable_ip_based_pooling = */ true,
            /* is_websocket = */ false, net_log_)) {
      using_spdy_ = true;
      next_state_ = STATE_SPDY_PROXY_CREATE_STREAM;
      return OK;
    }
  }
  next_state_ = STATE_SSL_CONNECT_COMPLETE;
  transport_socket_handle_.reset(new ClientSocketHandle());
  return transport_socket_handle_->Init(
      group_name_, ssl_params_, priority_, initial_socket_tag_, respect_limits_,
      base::Bind(&HttpProxyClientSocketWrapper::OnIOComplete,
                 base::Unretained(this)),
      ssl_pool_, net_log_);
}

int HttpProxyClientSocketWrapper::DoSSLConnectComplete(int result) {
  if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
    DCHECK(
        transport_socket_handle_->ssl_error_response_info().cert_request_info);
    UMA_HISTOGRAM_MEDIUM_TIMES("Net.HttpProxy.ConnectLatency.Secure.Error",
                               base::TimeTicks::Now() - connect_start_time_);
    error_response_info_.reset(new HttpResponseInfo(
        transport_socket_handle_->ssl_error_response_info()));
    error_response_info_->cert_request_info->is_proxy = true;
    return result;
  }

  if (IsCertificateError(result)) {
    UMA_HISTOGRAM_MEDIUM_TIMES("Net.HttpProxy.ConnectLatency.Secure.Error",
                               base::TimeTicks::Now() - connect_start_time_);
    if (ssl_params_->load_flags() & LOAD_IGNORE_ALL_CERT_ERRORS) {
      result = OK;
    } else {
      // TODO(rch): allow the user to deal with proxy cert errors in the
      // same way as server cert errors.
      transport_socket_handle_->socket()->Disconnect();
      return ERR_PROXY_CERTIFICATE_INVALID;
    }
  }
  // A SPDY session to the proxy completed prior to resolving the proxy
  // hostname. Surface this error, and allow the delegate to retry.
  // See crbug.com/334413.
  if (result == ERR_SPDY_SESSION_ALREADY_EXISTS) {
    DCHECK(!transport_socket_handle_->socket());
    return ERR_SPDY_SESSION_ALREADY_EXISTS;
  }
  if (result < 0) {
    UMA_HISTOGRAM_MEDIUM_TIMES("Net.HttpProxy.ConnectLatency.Secure.Error",
                               base::TimeTicks::Now() - connect_start_time_);
    if (transport_socket_handle_->socket())
      transport_socket_handle_->socket()->Disconnect();
    return ERR_PROXY_CONNECTION_FAILED;
  }

  negotiated_protocol_ =
      transport_socket_handle_->socket()->GetNegotiatedProtocol();
  using_spdy_ = negotiated_protocol_ == kProtoHTTP2;

  // Reset the timer to just the length of time allowed for HttpProxy handshake
  // so that a fast SSL connection plus a slow HttpProxy failure doesn't take
  // longer to timeout than it should.
  SetConnectTimer(proxy_negotiation_timeout_duration_);

  // TODO(rch): If we ever decide to implement a "trusted" SPDY proxy
  // (one that we speak SPDY over SSL to, but to which we send HTTPS
  // request directly instead of through CONNECT tunnels, then we
  // need to add a predicate to this if statement so we fall through
  // to the else case. (HttpProxyClientSocket currently acts as
  // a "trusted" SPDY proxy).
  if (using_spdy_ && tunnel_) {
    next_state_ = STATE_SPDY_PROXY_CREATE_STREAM;
  } else {
    next_state_ = STATE_HTTP_PROXY_CONNECT;
  }
  return result;
}

int HttpProxyClientSocketWrapper::DoHttpProxyConnect() {
  next_state_ = STATE_HTTP_PROXY_CONNECT_COMPLETE;

  if (transport_params_) {
    UMA_HISTOGRAM_MEDIUM_TIMES("Net.HttpProxy.ConnectLatency.Insecure.Success",
                               base::TimeTicks::Now() - connect_start_time_);
  } else {
    UMA_HISTOGRAM_MEDIUM_TIMES("Net.HttpProxy.ConnectLatency.Secure.Success",
                               base::TimeTicks::Now() - connect_start_time_);
  }

  // Add a HttpProxy connection on top of the tcp socket.
  transport_socket_ =
      transport_pool_->client_socket_factory()->CreateProxyClientSocket(
          std::move(transport_socket_handle_), user_agent_, endpoint_,
          http_auth_controller_.get(), tunnel_, using_spdy_,
          negotiated_protocol_, ssl_params_.get() != nullptr,
          traffic_annotation_);
  return transport_socket_->Connect(base::Bind(
      &HttpProxyClientSocketWrapper::OnIOComplete, base::Unretained(this)));
}

int HttpProxyClientSocketWrapper::DoHttpProxyConnectComplete(int result) {
  if (result == ERR_HTTP_1_1_REQUIRED)
    return ERR_PROXY_HTTP_1_1_REQUIRED;

  return result;
}

int HttpProxyClientSocketWrapper::DoSpdyProxyCreateStream() {
  DCHECK(using_spdy_);
  DCHECK(tunnel_);
  DCHECK(ssl_params_);
  SpdySessionKey key(
      ssl_params_->GetDirectConnectionParams()->destination().host_port_pair(),
      ProxyServer::Direct(), PRIVACY_MODE_DISABLED, initial_socket_tag_);
  base::WeakPtr<SpdySession> spdy_session =
      spdy_session_pool_->FindAvailableSession(
          key, /* enable_ip_based_pooling = */ true,
          /* is_websocket = */ false, net_log_);
  // It's possible that a session to the proxy has recently been created
  if (spdy_session) {
    if (transport_socket_handle_.get()) {
      if (transport_socket_handle_->socket())
        transport_socket_handle_->socket()->Disconnect();
      transport_socket_handle_->Reset();
    }
  } else {
    // Create a session direct to the proxy itself
    spdy_session = spdy_session_pool_->CreateAvailableSessionFromSocket(
        key, is_trusted_proxy_, std::move(transport_socket_handle_), net_log_);
    DCHECK(spdy_session);
  }

  next_state_ = STATE_SPDY_PROXY_CREATE_STREAM_COMPLETE;
  return spdy_stream_request_.StartRequest(
      SPDY_BIDIRECTIONAL_STREAM, spdy_session,
      GURL("https://" + endpoint_.ToString()), priority_, initial_socket_tag_,
      spdy_session->net_log(),
      base::Bind(&HttpProxyClientSocketWrapper::OnIOComplete,
                 base::Unretained(this)),
      traffic_annotation_);
}

int HttpProxyClientSocketWrapper::DoSpdyProxyCreateStreamComplete(int result) {
  if (result < 0)
    return result;

  next_state_ = STATE_HTTP_PROXY_CONNECT_COMPLETE;
  base::WeakPtr<SpdyStream> stream = spdy_stream_request_.ReleaseStream();
  DCHECK(stream.get());
  // |transport_socket_| will set itself as |stream|'s delegate.
  transport_socket_.reset(new SpdyProxyClientSocket(
      stream, user_agent_, endpoint_, net_log_, http_auth_controller_.get()));
  return transport_socket_->Connect(base::Bind(
      &HttpProxyClientSocketWrapper::OnIOComplete, base::Unretained(this)));
}

int HttpProxyClientSocketWrapper::DoQuicProxyCreateSession() {
  DCHECK(ssl_params_);
  DCHECK(tunnel_);
  next_state_ = STATE_QUIC_PROXY_CREATE_STREAM;
  const HostPortPair& proxy_server =
      ssl_params_->GetDirectConnectionParams()->destination().host_port_pair();
  return quic_stream_request_.Request(
      proxy_server, quic_version_, ssl_params_->privacy_mode(), priority_,
      initial_socket_tag_, ssl_params_->ssl_config().GetCertVerifyFlags(),
      GURL("https://" + proxy_server.ToString()), net_log_,
      &quic_net_error_details_,
      base::Bind(&HttpProxyClientSocketWrapper::OnIOComplete,
                 base::Unretained(this)));
}

int HttpProxyClientSocketWrapper::DoQuicProxyCreateStream(int result) {
  if (result < 0)
    return result;

  next_state_ = STATE_QUIC_PROXY_CREATE_STREAM_COMPLETE;
  quic_session_ = quic_stream_request_.ReleaseSessionHandle();
  return quic_session_->RequestStream(
      false,
      base::Bind(&HttpProxyClientSocketWrapper::OnIOComplete,
                 base::Unretained(this)),
      traffic_annotation_);
}

int HttpProxyClientSocketWrapper::DoQuicProxyCreateStreamComplete(int result) {
  if (result < 0)
    return result;

  next_state_ = STATE_HTTP_PROXY_CONNECT_COMPLETE;
  std::unique_ptr<QuicChromiumClientStream::Handle> quic_stream =
      quic_session_->ReleaseStream();

  spdy::SpdyPriority spdy_priority =
      ConvertRequestPriorityToQuicPriority(priority_);
  quic_stream->SetPriority(spdy_priority);

  transport_socket_.reset(new QuicProxyClientSocket(
      std::move(quic_stream), std::move(quic_session_), user_agent_, endpoint_,
      net_log_, http_auth_controller_.get()));
  return transport_socket_->Connect(base::Bind(
      &HttpProxyClientSocketWrapper::OnIOComplete, base::Unretained(this)));
}

int HttpProxyClientSocketWrapper::DoRestartWithAuth() {
  DCHECK(transport_socket_);

  next_state_ = STATE_RESTART_WITH_AUTH_COMPLETE;
  return transport_socket_->RestartWithAuth(base::BindOnce(
      &HttpProxyClientSocketWrapper::OnIOComplete, base::Unretained(this)));
}

int HttpProxyClientSocketWrapper::DoRestartWithAuthComplete(int result) {
  DCHECK_NE(ERR_IO_PENDING, result);

  // If the connection could not be reused to attempt to send proxy auth
  // credentials, try reconnecting. Do not reset the HttpAuthController in this
  // case; the server may, for instance, send "Proxy-Connection: close" and
  // expect that each leg of the authentication progress on separate
  // connections.
  bool reconnect = result == ERR_UNABLE_TO_REUSE_CONNECTION_FOR_PROXY_AUTH;

  // If auth credentials were sent but the connection was closed, the server may
  // have timed out while the user was selecting credentials. Retry once.
  if (!has_restarted_ &&
      (result == ERR_CONNECTION_CLOSED || result == ERR_CONNECTION_RESET ||
       result == ERR_CONNECTION_ABORTED ||
       result == ERR_SOCKET_NOT_CONNECTED)) {
    reconnect = true;
    has_restarted_ = true;

    // Release any auth state bound to the connection. The new connection will
    // start the current scheme from scratch.
    if (http_auth_controller_)
      http_auth_controller_->OnConnectionClosed();
  }

  if (reconnect) {
    // Attempt to create a new one.
    transport_socket_.reset();

    // Reconnect with HIGHEST priority to get in front of other requests that
    // don't yet have the information |http_auth_controller_| does.
    // TODO(mmenke): This may still result in waiting in line, if there are
    //               other HIGHEST priority requests. Consider a workaround for
    //               that. Starting the new request before releasing the old
    //               socket and using RespectLimits::Disabled would work,
    //               without exceding the the socket pool limits (Since the old
    //               socket would free up the extra socket slot when destroyed).
    priority_ = HIGHEST;
    next_state_ = STATE_BEGIN_CONNECT;
    return OK;
  }

  return result;
}

void HttpProxyClientSocketWrapper::SetConnectTimer(base::TimeDelta delay) {
  connect_timer_.Stop();
  connect_timer_.Start(FROM_HERE, delay, this,
                       &HttpProxyClientSocketWrapper::ConnectTimeout);
}

void HttpProxyClientSocketWrapper::ConnectTimeout() {
  // Timer shouldn't be running if next_state_ is STATE_NONE.
  DCHECK_NE(STATE_NONE, next_state_);
  DCHECK(!connect_callback_.is_null());

  if (next_state_ == STATE_TCP_CONNECT_COMPLETE ||
      next_state_ == STATE_SSL_CONNECT_COMPLETE) {
    if (transport_params_) {
      UMA_HISTOGRAM_MEDIUM_TIMES(
          "Net.HttpProxy.ConnectLatency.Insecure.TimedOut",
          base::TimeTicks::Now() - connect_start_time_);
    } else {
      UMA_HISTOGRAM_MEDIUM_TIMES("Net.HttpProxy.ConnectLatency.Secure.TimedOut",
                                 base::TimeTicks::Now() - connect_start_time_);
    }
  }

  CompletionOnceCallback callback = std::move(connect_callback_);
  Disconnect();
  std::move(callback).Run(ERR_CONNECTION_TIMED_OUT);
}

const HostResolver::RequestInfo&
HttpProxyClientSocketWrapper::GetDestination() {
  if (transport_params_) {
    return transport_params_->destination();
  } else {
    return ssl_params_->GetDirectConnectionParams()->destination();
  }
}

}  // namespace net
