Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / google_apis / gcm / engine / connection_factory_impl.cc
1 // Copyright (c) 2013 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 "google_apis/gcm/engine/connection_factory_impl.h"
6
7 #include "base/message_loop/message_loop.h"
8 #include "base/metrics/histogram.h"
9 #include "base/metrics/sparse_histogram.h"
10 #include "google_apis/gcm/engine/connection_handler_impl.h"
11 #include "google_apis/gcm/protocol/mcs.pb.h"
12 #include "net/base/net_errors.h"
13 #include "net/http/http_network_session.h"
14 #include "net/http/http_request_headers.h"
15 #include "net/proxy/proxy_info.h"
16 #include "net/socket/client_socket_handle.h"
17 #include "net/socket/client_socket_pool_manager.h"
18 #include "net/ssl/ssl_config_service.h"
19
20 namespace gcm {
21
22 namespace {
23
24 // The amount of time a Socket read should wait before timing out.
25 const int kReadTimeoutMs = 30000;  // 30 seconds.
26
27 // If a connection is reset after succeeding within this window of time,
28 // the previous backoff entry is restored (and the connection success is treated
29 // as if it was transient).
30 const int kConnectionResetWindowSecs = 10;  // 10 seconds.
31
32 // Decides whether the last login was within kConnectionResetWindowSecs of now
33 // or not.
34 bool ShouldRestorePreviousBackoff(const base::TimeTicks& login_time,
35                                   const base::TimeTicks& now_ticks) {
36   return !login_time.is_null() &&
37       now_ticks - login_time <=
38           base::TimeDelta::FromSeconds(kConnectionResetWindowSecs);
39 }
40
41 }  // namespace
42
43 ConnectionFactoryImpl::ConnectionFactoryImpl(
44     const GURL& mcs_endpoint,
45     const net::BackoffEntry::Policy& backoff_policy,
46     scoped_refptr<net::HttpNetworkSession> network_session,
47     net::NetLog* net_log)
48   : mcs_endpoint_(mcs_endpoint),
49     backoff_policy_(backoff_policy),
50     network_session_(network_session),
51     net_log_(net_log),
52     connecting_(false),
53     logging_in_(false),
54     weak_ptr_factory_(this) {
55 }
56
57 ConnectionFactoryImpl::~ConnectionFactoryImpl() {
58 }
59
60 void ConnectionFactoryImpl::Initialize(
61     const BuildLoginRequestCallback& request_builder,
62     const ConnectionHandler::ProtoReceivedCallback& read_callback,
63     const ConnectionHandler::ProtoSentCallback& write_callback) {
64   DCHECK(!connection_handler_);
65
66   previous_backoff_ = CreateBackoffEntry(&backoff_policy_);
67   backoff_entry_ = CreateBackoffEntry(&backoff_policy_);
68   request_builder_ = request_builder;
69
70   net::NetworkChangeNotifier::AddIPAddressObserver(this);
71   net::NetworkChangeNotifier::AddConnectionTypeObserver(this);
72   connection_handler_.reset(
73       new ConnectionHandlerImpl(
74           base::TimeDelta::FromMilliseconds(kReadTimeoutMs),
75           read_callback,
76           write_callback,
77           base::Bind(&ConnectionFactoryImpl::ConnectionHandlerCallback,
78                      weak_ptr_factory_.GetWeakPtr())));
79 }
80
81 ConnectionHandler* ConnectionFactoryImpl::GetConnectionHandler() const {
82   return connection_handler_.get();
83 }
84
85 void ConnectionFactoryImpl::Connect() {
86   DCHECK(connection_handler_);
87
88   connecting_ = true;
89   if (backoff_entry_->ShouldRejectRequest()) {
90     DVLOG(1) << "Delaying MCS endpoint connection for "
91              << backoff_entry_->GetTimeUntilRelease().InMilliseconds()
92              << " milliseconds.";
93     base::MessageLoop::current()->PostDelayedTask(
94         FROM_HERE,
95         base::Bind(&ConnectionFactoryImpl::Connect,
96                    weak_ptr_factory_.GetWeakPtr()),
97         backoff_entry_->GetTimeUntilRelease());
98     return;
99   }
100
101   DVLOG(1) << "Attempting connection to MCS endpoint.";
102   ConnectImpl();
103 }
104
105 bool ConnectionFactoryImpl::IsEndpointReachable() const {
106   return connection_handler_ &&
107       connection_handler_->CanSendMessage() &&
108       !connecting_;
109 }
110
111 void ConnectionFactoryImpl::SignalConnectionReset(
112     ConnectionResetReason reason) {
113   // A failure can trigger multiple resets, so no need to do anything if a
114   // connection is already in progress.
115   if (connecting_)
116     return;
117
118   UMA_HISTOGRAM_ENUMERATION("GCM.ConnectionResetReason",
119                             reason,
120                             CONNECTION_RESET_COUNT);
121   if (!last_login_time_.is_null()) {
122     UMA_HISTOGRAM_CUSTOM_TIMES("GCM.ConnectionUpTime",
123                                NowTicks() - last_login_time_,
124                                base::TimeDelta::FromSeconds(1),
125                                base::TimeDelta::FromHours(24),
126                                50);
127     // |last_login_time_| will be reset below, before attempting the new
128     // connection.
129   }
130
131   if (connection_handler_)
132     connection_handler_->Reset();
133
134   if (socket_handle_.socket() && socket_handle_.socket()->IsConnected())
135     socket_handle_.socket()->Disconnect();
136   socket_handle_.Reset();
137
138   if (logging_in_) {
139     // Failures prior to login completion just reuse the existing backoff entry.
140     logging_in_ = false;
141     backoff_entry_->InformOfRequest(false);
142   } else if (reason == LOGIN_FAILURE ||
143              ShouldRestorePreviousBackoff(last_login_time_, NowTicks())) {
144     // Failures due to login, or within the reset window of a login, restore
145     // the backoff entry that was saved off at login completion time.
146     backoff_entry_.swap(previous_backoff_);
147     backoff_entry_->InformOfRequest(false);
148   } else {
149     // We shouldn't be in backoff in thise case.
150     DCHECK(backoff_entry_->CanDiscard());
151   }
152
153   // At this point the last login time has been consumed or deemed irrelevant,
154   // reset it.
155   last_login_time_ = base::TimeTicks();
156
157   Connect();
158 }
159
160 base::TimeTicks ConnectionFactoryImpl::NextRetryAttempt() const {
161   if (!backoff_entry_)
162     return base::TimeTicks();
163   return backoff_entry_->GetReleaseTime();
164 }
165
166 void ConnectionFactoryImpl::OnConnectionTypeChanged(
167     net::NetworkChangeNotifier::ConnectionType type) {
168   if (type == net::NetworkChangeNotifier::CONNECTION_NONE)
169     return;
170
171   // TODO(zea): implement different backoff/retry policies based on connection
172   // type.
173   DVLOG(1) << "Connection type changed to " << type << ", resetting backoff.";
174   backoff_entry_->Reset();
175   // Connect(..) should be retrying with backoff already if a connection is
176   // necessary, so no need to call again.
177 }
178
179 void ConnectionFactoryImpl::OnIPAddressChanged() {
180   DVLOG(1) << "IP Address changed, resetting backoff.";
181   backoff_entry_->Reset();
182   // Connect(..) should be retrying with backoff already if a connection is
183   // necessary, so no need to call again.
184 }
185
186 void ConnectionFactoryImpl::ConnectImpl() {
187   DCHECK(connecting_);
188   DCHECK(!socket_handle_.socket());
189
190   // TODO(zea): resolve proxies.
191   net::ProxyInfo proxy_info;
192   proxy_info.UseDirect();
193   net::SSLConfig ssl_config;
194   network_session_->ssl_config_service()->GetSSLConfig(&ssl_config);
195
196   int status = net::InitSocketHandleForTlsConnect(
197       net::HostPortPair::FromURL(mcs_endpoint_),
198       network_session_.get(),
199       proxy_info,
200       ssl_config,
201       ssl_config,
202       net::kPrivacyModeDisabled,
203       net::BoundNetLog::Make(net_log_, net::NetLog::SOURCE_SOCKET),
204       &socket_handle_,
205       base::Bind(&ConnectionFactoryImpl::OnConnectDone,
206                  weak_ptr_factory_.GetWeakPtr()));
207   if (status != net::ERR_IO_PENDING)
208     OnConnectDone(status);
209 }
210
211 void ConnectionFactoryImpl::InitHandler() {
212   // May be null in tests.
213   mcs_proto::LoginRequest login_request;
214   if (!request_builder_.is_null()) {
215     request_builder_.Run(&login_request);
216     DCHECK(login_request.IsInitialized());
217   }
218
219   connection_handler_->Init(login_request, socket_handle_.socket());
220 }
221
222 scoped_ptr<net::BackoffEntry> ConnectionFactoryImpl::CreateBackoffEntry(
223     const net::BackoffEntry::Policy* const policy) {
224   return scoped_ptr<net::BackoffEntry>(new net::BackoffEntry(policy));
225 }
226
227 base::TimeTicks ConnectionFactoryImpl::NowTicks() {
228   return base::TimeTicks::Now();
229 }
230
231 void ConnectionFactoryImpl::OnConnectDone(int result) {
232   UMA_HISTOGRAM_BOOLEAN("GCM.ConnectionSuccessRate", (result == net::OK));
233
234   if (result != net::OK) {
235     LOG(ERROR) << "Failed to connect to MCS endpoint with error " << result;
236     backoff_entry_->InformOfRequest(false);
237     UMA_HISTOGRAM_SPARSE_SLOWLY("GCM.ConnectionFailureErrorCode", result);
238     Connect();
239     return;
240   }
241
242   connecting_ = false;
243   logging_in_ = true;
244   DVLOG(1) << "MCS endpoint socket connection success, starting login.";
245   InitHandler();
246 }
247
248 void ConnectionFactoryImpl::ConnectionHandlerCallback(int result) {
249   DCHECK(!connecting_);
250   if (result != net::OK) {
251     // TODO(zea): Consider how to handle errors that may require some sort of
252     // user intervention (login page, etc.).
253     UMA_HISTOGRAM_SPARSE_SLOWLY("GCM.ConnectionDisconnectErrorCode", result);
254     SignalConnectionReset(SOCKET_FAILURE);
255     return;
256   }
257
258   // Handshake complete, reset backoff. If the login failed with an error,
259   // the client should invoke SignalConnectionReset(LOGIN_FAILURE), which will
260   // restore the previous backoff.
261   last_login_time_ = NowTicks();
262   previous_backoff_.swap(backoff_entry_);
263   backoff_entry_->Reset();
264   logging_in_ = false;
265 }
266
267 }  // namespace gcm