1// Copyright 2014 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 "net/socket/websocket_transport_connect_sub_job.h"
6
7#include "base/logging.h"
8#include "net/base/ip_endpoint.h"
9#include "net/base/net_errors.h"
10#include "net/base/net_log.h"
11#include "net/socket/client_socket_factory.h"
12#include "net/socket/websocket_endpoint_lock_manager.h"
13
14namespace net {
15
16WebSocketTransportConnectSubJob::WebSocketTransportConnectSubJob(
17    const AddressList& addresses,
18    WebSocketTransportConnectJob* parent_job,
19    SubJobType type)
20    : parent_job_(parent_job),
21      addresses_(addresses),
22      current_address_index_(0),
23      next_state_(STATE_NONE),
24      type_(type) {}
25
26WebSocketTransportConnectSubJob::~WebSocketTransportConnectSubJob() {
27  // We don't worry about cancelling the TCP connect, since ~StreamSocket will
28  // take care of it.
29  if (next()) {
30    DCHECK_EQ(STATE_OBTAIN_LOCK_COMPLETE, next_state_);
31    // The ~Waiter destructor will remove this object from the waiting list.
32  } else if (next_state_ == STATE_TRANSPORT_CONNECT_COMPLETE) {
33    WebSocketEndpointLockManager::GetInstance()->UnlockEndpoint(
34        CurrentAddress());
35  }
36}
37
38// Start connecting.
39int WebSocketTransportConnectSubJob::Start() {
40  DCHECK_EQ(STATE_NONE, next_state_);
41  next_state_ = STATE_OBTAIN_LOCK;
42  return DoLoop(OK);
43}
44
45// Called by WebSocketEndpointLockManager when the lock becomes available.
46void WebSocketTransportConnectSubJob::GotEndpointLock() {
47  DCHECK_EQ(STATE_OBTAIN_LOCK_COMPLETE, next_state_);
48  OnIOComplete(OK);
49}
50
51LoadState WebSocketTransportConnectSubJob::GetLoadState() const {
52  switch (next_state_) {
53    case STATE_OBTAIN_LOCK:
54    case STATE_OBTAIN_LOCK_COMPLETE:
55      // TODO(ricea): Add a WebSocket-specific LOAD_STATE ?
56      return LOAD_STATE_WAITING_FOR_AVAILABLE_SOCKET;
57    case STATE_TRANSPORT_CONNECT:
58    case STATE_TRANSPORT_CONNECT_COMPLETE:
59    case STATE_DONE:
60      return LOAD_STATE_CONNECTING;
61    case STATE_NONE:
62      return LOAD_STATE_IDLE;
63  }
64  NOTREACHED();
65  return LOAD_STATE_IDLE;
66}
67
68ClientSocketFactory* WebSocketTransportConnectSubJob::client_socket_factory()
69    const {
70  return parent_job_->helper_.client_socket_factory();
71}
72
73const BoundNetLog& WebSocketTransportConnectSubJob::net_log() const {
74  return parent_job_->net_log();
75}
76
77const IPEndPoint& WebSocketTransportConnectSubJob::CurrentAddress() const {
78  DCHECK_LT(current_address_index_, addresses_.size());
79  return addresses_[current_address_index_];
80}
81
82void WebSocketTransportConnectSubJob::OnIOComplete(int result) {
83  int rv = DoLoop(result);
84  if (rv != ERR_IO_PENDING)
85    parent_job_->OnSubJobComplete(rv, this);  // |this| deleted
86}
87
88int WebSocketTransportConnectSubJob::DoLoop(int result) {
89  DCHECK_NE(next_state_, STATE_NONE);
90
91  int rv = result;
92  do {
93    State state = next_state_;
94    next_state_ = STATE_NONE;
95    switch (state) {
96      case STATE_OBTAIN_LOCK:
97        DCHECK_EQ(OK, rv);
98        rv = DoEndpointLock();
99        break;
100      case STATE_OBTAIN_LOCK_COMPLETE:
101        DCHECK_EQ(OK, rv);
102        rv = DoEndpointLockComplete();
103        break;
104      case STATE_TRANSPORT_CONNECT:
105        DCHECK_EQ(OK, rv);
106        rv = DoTransportConnect();
107        break;
108      case STATE_TRANSPORT_CONNECT_COMPLETE:
109        rv = DoTransportConnectComplete(rv);
110        break;
111      default:
112        NOTREACHED();
113        rv = ERR_FAILED;
114        break;
115    }
116  } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE &&
117           next_state_ != STATE_DONE);
118
119  return rv;
120}
121
122int WebSocketTransportConnectSubJob::DoEndpointLock() {
123  int rv = WebSocketEndpointLockManager::GetInstance()->LockEndpoint(
124      CurrentAddress(), this);
125  next_state_ = STATE_OBTAIN_LOCK_COMPLETE;
126  return rv;
127}
128
129int WebSocketTransportConnectSubJob::DoEndpointLockComplete() {
130  next_state_ = STATE_TRANSPORT_CONNECT;
131  return OK;
132}
133
134int WebSocketTransportConnectSubJob::DoTransportConnect() {
135  // TODO(ricea): Update global g_last_connect_time and report
136  // ConnectInterval.
137  next_state_ = STATE_TRANSPORT_CONNECT_COMPLETE;
138  AddressList one_address(CurrentAddress());
139  transport_socket_ = client_socket_factory()->CreateTransportClientSocket(
140      one_address, net_log().net_log(), net_log().source());
141  // This use of base::Unretained() is safe because transport_socket_ is
142  // destroyed in the destructor.
143  return transport_socket_->Connect(base::Bind(
144      &WebSocketTransportConnectSubJob::OnIOComplete, base::Unretained(this)));
145}
146
147int WebSocketTransportConnectSubJob::DoTransportConnectComplete(int result) {
148  next_state_ = STATE_DONE;
149  WebSocketEndpointLockManager* endpoint_lock_manager =
150      WebSocketEndpointLockManager::GetInstance();
151  if (result != OK) {
152    endpoint_lock_manager->UnlockEndpoint(CurrentAddress());
153
154    if (current_address_index_ + 1 < addresses_.size()) {
155      // Try falling back to the next address in the list.
156      next_state_ = STATE_OBTAIN_LOCK;
157      ++current_address_index_;
158      result = OK;
159    }
160
161    return result;
162  }
163
164  endpoint_lock_manager->RememberSocket(transport_socket_.get(),
165                                        CurrentAddress());
166
167  return result;
168}
169
170}  // namespace net
171