// Copyright 2014 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/socket/websocket_transport_connect_sub_job.h" #include "base/logging.h" #include "net/base/ip_endpoint.h" #include "net/base/net_errors.h" #include "net/log/net_log_with_source.h" #include "net/socket/client_socket_factory.h" #include "net/socket/websocket_endpoint_lock_manager.h" namespace net { WebSocketTransportConnectSubJob::WebSocketTransportConnectSubJob( const AddressList& addresses, WebSocketTransportConnectJob* parent_job, SubJobType type, WebSocketEndpointLockManager* websocket_endpoint_lock_manager) : parent_job_(parent_job), addresses_(addresses), current_address_index_(0), next_state_(STATE_NONE), type_(type), websocket_endpoint_lock_manager_(websocket_endpoint_lock_manager) {} WebSocketTransportConnectSubJob::~WebSocketTransportConnectSubJob() { // We don't worry about cancelling the TCP connect, since ~StreamSocket will // take care of it. if (next()) { DCHECK_EQ(STATE_OBTAIN_LOCK_COMPLETE, next_state_); // The ~Waiter destructor will remove this object from the waiting list. } else if (next_state_ == STATE_TRANSPORT_CONNECT_COMPLETE) { websocket_endpoint_lock_manager_->UnlockEndpoint(CurrentAddress()); } } // Start connecting. int WebSocketTransportConnectSubJob::Start() { DCHECK_EQ(STATE_NONE, next_state_); next_state_ = STATE_OBTAIN_LOCK; return DoLoop(OK); } // Called by WebSocketEndpointLockManager when the lock becomes available. void WebSocketTransportConnectSubJob::GotEndpointLock() { DCHECK_EQ(STATE_OBTAIN_LOCK_COMPLETE, next_state_); OnIOComplete(OK); } LoadState WebSocketTransportConnectSubJob::GetLoadState() const { switch (next_state_) { case STATE_OBTAIN_LOCK: case STATE_OBTAIN_LOCK_COMPLETE: // TODO(ricea): Add a WebSocket-specific LOAD_STATE ? return LOAD_STATE_WAITING_FOR_AVAILABLE_SOCKET; case STATE_TRANSPORT_CONNECT: case STATE_TRANSPORT_CONNECT_COMPLETE: case STATE_DONE: return LOAD_STATE_CONNECTING; case STATE_NONE: return LOAD_STATE_IDLE; } NOTREACHED(); return LOAD_STATE_IDLE; } ClientSocketFactory* WebSocketTransportConnectSubJob::client_socket_factory() const { return parent_job_->client_socket_factory_; } const NetLogWithSource& WebSocketTransportConnectSubJob::net_log() const { return parent_job_->net_log(); } const IPEndPoint& WebSocketTransportConnectSubJob::CurrentAddress() const { DCHECK_LT(current_address_index_, addresses_.size()); return addresses_[current_address_index_]; } void WebSocketTransportConnectSubJob::OnIOComplete(int result) { int rv = DoLoop(result); if (rv != ERR_IO_PENDING) parent_job_->OnSubJobComplete(rv, this); // |this| deleted } int WebSocketTransportConnectSubJob::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_OBTAIN_LOCK: DCHECK_EQ(OK, rv); rv = DoEndpointLock(); break; case STATE_OBTAIN_LOCK_COMPLETE: DCHECK_EQ(OK, rv); rv = DoEndpointLockComplete(); break; case STATE_TRANSPORT_CONNECT: DCHECK_EQ(OK, rv); rv = DoTransportConnect(); break; case STATE_TRANSPORT_CONNECT_COMPLETE: rv = DoTransportConnectComplete(rv); break; default: NOTREACHED(); rv = ERR_FAILED; break; } } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE && next_state_ != STATE_DONE); return rv; } int WebSocketTransportConnectSubJob::DoEndpointLock() { int rv = websocket_endpoint_lock_manager_->LockEndpoint(CurrentAddress(), this); next_state_ = STATE_OBTAIN_LOCK_COMPLETE; return rv; } int WebSocketTransportConnectSubJob::DoEndpointLockComplete() { next_state_ = STATE_TRANSPORT_CONNECT; return OK; } int WebSocketTransportConnectSubJob::DoTransportConnect() { // TODO(ricea): Update global g_last_connect_time and report // ConnectInterval. next_state_ = STATE_TRANSPORT_CONNECT_COMPLETE; AddressList one_address(CurrentAddress()); transport_socket_ = client_socket_factory()->CreateTransportClientSocket( one_address, nullptr, net_log().net_log(), net_log().source()); // This use of base::Unretained() is safe because transport_socket_ is // destroyed in the destructor. return transport_socket_->Connect(base::Bind( &WebSocketTransportConnectSubJob::OnIOComplete, base::Unretained(this))); } int WebSocketTransportConnectSubJob::DoTransportConnectComplete(int result) { next_state_ = STATE_DONE; if (result != OK) { websocket_endpoint_lock_manager_->UnlockEndpoint(CurrentAddress()); if (current_address_index_ + 1 < addresses_.size()) { // Try falling back to the next address in the list. next_state_ = STATE_OBTAIN_LOCK; ++current_address_index_; result = OK; } return result; } websocket_endpoint_lock_manager_->RememberSocket(transport_socket_.get(), CurrentAddress()); return result; } } // namespace net