Upstream version 5.34.92.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 // Backoff policy.
33 const net::BackoffEntry::Policy kConnectionBackoffPolicy = {
34   // Number of initial errors (in sequence) to ignore before applying
35   // exponential back-off rules.
36   0,
37
38   // Initial delay for exponential back-off in ms.
39   10000,  // 10 seconds.
40
41   // Factor by which the waiting time will be multiplied.
42   2,
43
44   // Fuzzing percentage. ex: 10% will spread requests randomly
45   // between 90%-100% of the calculated time.
46   0.5,  // 50%.
47
48   // Maximum amount of time we are willing to delay our request in ms.
49   1000 * 60 * 5, // 5 minutes.
50
51   // Time to keep an entry from being discarded even when it
52   // has no significant state, -1 to never discard.
53   -1,
54
55   // Don't use initial delay unless the last request was an error.
56   false,
57 };
58
59 }  // namespace
60
61 ConnectionFactoryImpl::ConnectionFactoryImpl(
62     const GURL& mcs_endpoint,
63     scoped_refptr<net::HttpNetworkSession> network_session,
64     net::NetLog* net_log)
65   : mcs_endpoint_(mcs_endpoint),
66     network_session_(network_session),
67     net_log_(net_log),
68     connecting_(false),
69     weak_ptr_factory_(this) {
70 }
71
72 ConnectionFactoryImpl::~ConnectionFactoryImpl() {
73 }
74
75 void ConnectionFactoryImpl::Initialize(
76     const BuildLoginRequestCallback& request_builder,
77     const ConnectionHandler::ProtoReceivedCallback& read_callback,
78     const ConnectionHandler::ProtoSentCallback& write_callback) {
79   DCHECK(!connection_handler_);
80
81   previous_backoff_ = CreateBackoffEntry(&kConnectionBackoffPolicy);
82   backoff_entry_ = CreateBackoffEntry(&kConnectionBackoffPolicy);
83   request_builder_ = request_builder;
84
85   net::NetworkChangeNotifier::AddIPAddressObserver(this);
86   net::NetworkChangeNotifier::AddConnectionTypeObserver(this);
87   connection_handler_.reset(
88       new ConnectionHandlerImpl(
89           base::TimeDelta::FromMilliseconds(kReadTimeoutMs),
90           read_callback,
91           write_callback,
92           base::Bind(&ConnectionFactoryImpl::ConnectionHandlerCallback,
93                      weak_ptr_factory_.GetWeakPtr())));
94 }
95
96 ConnectionHandler* ConnectionFactoryImpl::GetConnectionHandler() const {
97   return connection_handler_.get();
98 }
99
100 void ConnectionFactoryImpl::Connect() {
101   DCHECK(connection_handler_);
102
103   connecting_ = true;
104   if (backoff_entry_->ShouldRejectRequest()) {
105     DVLOG(1) << "Delaying MCS endpoint connection for "
106              << backoff_entry_->GetTimeUntilRelease().InMilliseconds()
107              << " milliseconds.";
108     base::MessageLoop::current()->PostDelayedTask(
109         FROM_HERE,
110         base::Bind(&ConnectionFactoryImpl::Connect,
111                    weak_ptr_factory_.GetWeakPtr()),
112         backoff_entry_->GetTimeUntilRelease());
113     return;
114   }
115
116   DVLOG(1) << "Attempting connection to MCS endpoint.";
117   ConnectImpl();
118 }
119
120 bool ConnectionFactoryImpl::IsEndpointReachable() const {
121   return connection_handler_ &&
122       connection_handler_->CanSendMessage() &&
123       !connecting_;
124 }
125
126 void ConnectionFactoryImpl::SignalConnectionReset() {
127   if (connecting_)
128     return;  // Already attempting to reconnect.
129
130   if (!backoff_reset_time_.is_null() &&
131       NowTicks() - backoff_reset_time_ <=
132           base::TimeDelta::FromSeconds(kConnectionResetWindowSecs)) {
133     backoff_entry_.swap(previous_backoff_);
134     backoff_entry_->InformOfRequest(false);
135   }
136   backoff_reset_time_ = base::TimeTicks();
137   previous_backoff_->Reset();
138   Connect();
139 }
140
141 base::TimeTicks ConnectionFactoryImpl::NextRetryAttempt() const {
142   if (!backoff_entry_)
143     return base::TimeTicks();
144   return backoff_entry_->GetReleaseTime();
145 }
146
147 void ConnectionFactoryImpl::OnConnectionTypeChanged(
148     net::NetworkChangeNotifier::ConnectionType type) {
149   if (type == net::NetworkChangeNotifier::CONNECTION_NONE)
150     return;
151
152   // TODO(zea): implement different backoff/retry policies based on connection
153   // type.
154   DVLOG(1) << "Connection type changed to " << type << ", resetting backoff.";
155   backoff_entry_->Reset();
156   // Connect(..) should be retrying with backoff already if a connection is
157   // necessary, so no need to call again.
158 }
159
160 void ConnectionFactoryImpl::OnIPAddressChanged() {
161   DVLOG(1) << "IP Address changed, resetting backoff.";
162   backoff_entry_->Reset();
163   // Connect(..) should be retrying with backoff already if a connection is
164   // necessary, so no need to call again.
165 }
166
167 void ConnectionFactoryImpl::ConnectImpl() {
168   if (socket_handle_.socket() && socket_handle_.socket()->IsConnected())
169     socket_handle_.socket()->Disconnect();
170   socket_handle_.Reset();
171
172   // TODO(zea): resolve proxies.
173   net::ProxyInfo proxy_info;
174   proxy_info.UseDirect();
175   net::SSLConfig ssl_config;
176   network_session_->ssl_config_service()->GetSSLConfig(&ssl_config);
177
178   int status = net::InitSocketHandleForTlsConnect(
179       net::HostPortPair::FromURL(mcs_endpoint_),
180       network_session_.get(),
181       proxy_info,
182       ssl_config,
183       ssl_config,
184       net::kPrivacyModeDisabled,
185       net::BoundNetLog::Make(net_log_, net::NetLog::SOURCE_SOCKET),
186       &socket_handle_,
187       base::Bind(&ConnectionFactoryImpl::OnConnectDone,
188                  weak_ptr_factory_.GetWeakPtr()));
189   if (status != net::ERR_IO_PENDING)
190     OnConnectDone(status);
191 }
192
193 void ConnectionFactoryImpl::InitHandler() {
194   // May be null in tests.
195   mcs_proto::LoginRequest login_request;
196   if (!request_builder_.is_null()) {
197     request_builder_.Run(&login_request);
198     DCHECK(login_request.IsInitialized());
199   }
200
201   connection_handler_->Init(login_request, socket_handle_.socket());
202 }
203
204 scoped_ptr<net::BackoffEntry> ConnectionFactoryImpl::CreateBackoffEntry(
205     const net::BackoffEntry::Policy* const policy) {
206   return scoped_ptr<net::BackoffEntry>(new net::BackoffEntry(policy));
207 }
208
209 base::TimeTicks ConnectionFactoryImpl::NowTicks() {
210   return base::TimeTicks::Now();
211 }
212
213 void ConnectionFactoryImpl::OnConnectDone(int result) {
214   if (result != net::OK) {
215     LOG(ERROR) << "Failed to connect to MCS endpoint with error " << result;
216     backoff_entry_->InformOfRequest(false);
217     UMA_HISTOGRAM_SPARSE_SLOWLY("GCM.ConnectionFailureErrorCode", result);
218     Connect();
219     return;
220   }
221
222   DVLOG(1) << "MCS endpoint socket connection success, starting handshake.";
223   InitHandler();
224 }
225
226 void ConnectionFactoryImpl::ConnectionHandlerCallback(int result) {
227   if (result == net::OK) {
228     // Handshake succeeded, reset the backoff.
229     connecting_ = false;
230     backoff_reset_time_ = NowTicks();
231     previous_backoff_.swap(backoff_entry_);
232     backoff_entry_->Reset();
233     return;
234   }
235
236   if (!connecting_)
237     UMA_HISTOGRAM_SPARSE_SLOWLY("GCM.ConnectionDisconnectErrorCode", result);
238
239   // TODO(zea): Consider how to handle errors that may require some sort of
240   // user intervention (login page, etc.).
241   LOG(ERROR) << "Connection reset with error " << result;
242   backoff_entry_->InformOfRequest(false);
243   Connect();
244 }
245
246 }  // namespace gcm