Roll src/third_party/WebKit 9f7fb92:f103b33 (svn 202621:202622)
[chromium-blink-merge.git] / components / invalidation / impl / gcm_network_channel.cc
blob7e66446480778614e0409a6afea84d3c145c5b7b
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.
5 #include "base/base64.h"
6 #include "base/i18n/time_formatting.h"
7 #include "base/location.h"
8 #include "base/metrics/histogram_macros.h"
9 #include "base/sha1.h"
10 #include "base/single_thread_task_runner.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/string_util.h"
13 #include "base/thread_task_runner_handle.h"
14 #if !defined(OS_ANDROID)
15 // channel_common.proto defines ANDROID constant that conflicts with Android
16 // build. At the same time TiclInvalidationService is not used on Android so it
17 // is safe to exclude these protos from Android build.
18 #include "google/cacheinvalidation/android_channel.pb.h"
19 #include "google/cacheinvalidation/channel_common.pb.h"
20 #include "google/cacheinvalidation/types.pb.h"
21 #endif
22 #include "components/data_use_measurement/core/data_use_user_data.h"
23 #include "components/invalidation/impl/gcm_network_channel.h"
24 #include "components/invalidation/impl/gcm_network_channel_delegate.h"
25 #include "google_apis/gaia/google_service_auth_error.h"
26 #include "net/http/http_status_code.h"
27 #include "net/url_request/url_fetcher.h"
28 #include "net/url_request/url_request_status.h"
30 namespace syncer {
32 namespace {
34 const char kCacheInvalidationEndpointUrl[] =
35 "https://clients4.google.com/invalidation/android/request/";
36 const char kCacheInvalidationPackageName[] = "com.google.chrome.invalidations";
38 // Register backoff policy.
39 const net::BackoffEntry::Policy kRegisterBackoffPolicy = {
40 // Number of initial errors (in sequence) to ignore before applying
41 // exponential back-off rules.
44 // Initial delay for exponential back-off in ms.
45 2000, // 2 seconds.
47 // Factor by which the waiting time will be multiplied.
50 // Fuzzing percentage. ex: 10% will spread requests randomly
51 // between 90%-100% of the calculated time.
52 0.2, // 20%.
54 // Maximum amount of time we are willing to delay our request in ms.
55 1000 * 3600 * 4, // 4 hours.
57 // Time to keep an entry from being discarded even when it
58 // has no significant state, -1 to never discard.
59 -1,
61 // Don't use initial delay unless the last request was an error.
62 false,
65 // Incoming message status values for UMA_HISTOGRAM.
66 enum IncomingMessageStatus {
67 INCOMING_MESSAGE_SUCCESS,
68 MESSAGE_EMPTY, // GCM message's content is missing or empty.
69 INVALID_ENCODING, // Base64Decode failed.
70 INVALID_PROTO, // Parsing protobuf failed.
72 // This enum is used in UMA_HISTOGRAM_ENUMERATION. Insert new values above
73 // this line.
74 INCOMING_MESSAGE_STATUS_COUNT
77 // Outgoing message status values for UMA_HISTOGRAM.
78 enum OutgoingMessageStatus {
79 OUTGOING_MESSAGE_SUCCESS,
80 MESSAGE_DISCARDED, // New message started before old one was sent.
81 ACCESS_TOKEN_FAILURE, // Requeting access token failed.
82 POST_FAILURE, // HTTP Post failed.
84 // This enum is used in UMA_HISTOGRAM_ENUMERATION. Insert new values above
85 // this line.
86 OUTGOING_MESSAGE_STATUS_COUNT
89 const char kIncomingMessageStatusHistogram[] =
90 "GCMInvalidations.IncomingMessageStatus";
91 const char kOutgoingMessageStatusHistogram[] =
92 "GCMInvalidations.OutgoingMessageStatus";
94 void RecordIncomingMessageStatus(IncomingMessageStatus status) {
95 UMA_HISTOGRAM_ENUMERATION(kIncomingMessageStatusHistogram,
96 status,
97 INCOMING_MESSAGE_STATUS_COUNT);
100 void RecordOutgoingMessageStatus(OutgoingMessageStatus status) {
101 UMA_HISTOGRAM_ENUMERATION(kOutgoingMessageStatusHistogram,
102 status,
103 OUTGOING_MESSAGE_STATUS_COUNT);
106 } // namespace
108 GCMNetworkChannel::GCMNetworkChannel(
109 scoped_refptr<net::URLRequestContextGetter> request_context_getter,
110 scoped_ptr<GCMNetworkChannelDelegate> delegate)
111 : request_context_getter_(request_context_getter),
112 delegate_(delegate.Pass()),
113 register_backoff_entry_(new net::BackoffEntry(&kRegisterBackoffPolicy)),
114 gcm_channel_online_(false),
115 http_channel_online_(false),
116 diagnostic_info_(this),
117 weak_factory_(this) {
118 net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
119 delegate_->Initialize(base::Bind(&GCMNetworkChannel::OnConnectionStateChanged,
120 weak_factory_.GetWeakPtr()));
121 Register();
124 GCMNetworkChannel::~GCMNetworkChannel() {
125 net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
128 void GCMNetworkChannel::Register() {
129 delegate_->Register(base::Bind(&GCMNetworkChannel::OnRegisterComplete,
130 weak_factory_.GetWeakPtr()));
133 void GCMNetworkChannel::OnRegisterComplete(
134 const std::string& registration_id,
135 gcm::GCMClient::Result result) {
136 DCHECK(CalledOnValidThread());
137 if (result == gcm::GCMClient::SUCCESS) {
138 DCHECK(!registration_id.empty());
139 DVLOG(2) << "Got registration_id";
140 register_backoff_entry_->Reset();
141 registration_id_ = registration_id;
142 if (!cached_message_.empty())
143 RequestAccessToken();
144 } else {
145 DVLOG(2) << "Register failed: " << result;
146 // Retry in case of transient error.
147 switch (result) {
148 case gcm::GCMClient::NETWORK_ERROR:
149 case gcm::GCMClient::SERVER_ERROR:
150 case gcm::GCMClient::TTL_EXCEEDED:
151 case gcm::GCMClient::UNKNOWN_ERROR: {
152 register_backoff_entry_->InformOfRequest(false);
153 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
154 FROM_HERE, base::Bind(&GCMNetworkChannel::Register,
155 weak_factory_.GetWeakPtr()),
156 register_backoff_entry_->GetTimeUntilRelease());
157 break;
159 default:
160 break;
163 diagnostic_info_.registration_id_ = registration_id_;
164 diagnostic_info_.registration_result_ = result;
167 void GCMNetworkChannel::SendMessage(const std::string& message) {
168 DCHECK(CalledOnValidThread());
169 DCHECK(!message.empty());
170 DVLOG(2) << "SendMessage";
171 diagnostic_info_.sent_messages_count_++;
172 if (!cached_message_.empty()) {
173 RecordOutgoingMessageStatus(MESSAGE_DISCARDED);
175 cached_message_ = message;
177 if (!registration_id_.empty()) {
178 RequestAccessToken();
182 void GCMNetworkChannel::RequestAccessToken() {
183 DCHECK(CalledOnValidThread());
184 delegate_->RequestToken(base::Bind(&GCMNetworkChannel::OnGetTokenComplete,
185 weak_factory_.GetWeakPtr()));
188 void GCMNetworkChannel::OnGetTokenComplete(
189 const GoogleServiceAuthError& error,
190 const std::string& token) {
191 DCHECK(CalledOnValidThread());
192 if (cached_message_.empty()) {
193 // Nothing to do.
194 return;
197 if (error.state() != GoogleServiceAuthError::NONE) {
198 // Requesting access token failed. Persistent errors will be reported by
199 // token service. Just drop this request, cacheinvalidations will retry
200 // sending message and at that time we'll retry requesting access token.
201 DVLOG(1) << "RequestAccessToken failed: " << error.ToString();
202 RecordOutgoingMessageStatus(ACCESS_TOKEN_FAILURE);
203 // Message won't get sent. Notify that http channel doesn't work.
204 UpdateHttpChannelState(false);
205 cached_message_.clear();
206 return;
208 DCHECK(!token.empty());
209 // Save access token in case POST fails and we need to invalidate it.
210 access_token_ = token;
212 DVLOG(2) << "Got access token, sending message";
213 fetcher_ = net::URLFetcher::Create(BuildUrl(registration_id_),
214 net::URLFetcher::POST, this);
215 data_use_measurement::DataUseUserData::AttachToFetcher(
216 fetcher_.get(), data_use_measurement::DataUseUserData::INVALIDATION);
217 fetcher_->SetRequestContext(request_context_getter_.get());
218 const std::string auth_header("Authorization: Bearer " + access_token_);
219 fetcher_->AddExtraRequestHeader(auth_header);
220 if (!echo_token_.empty()) {
221 const std::string echo_header("echo-token: " + echo_token_);
222 fetcher_->AddExtraRequestHeader(echo_header);
224 fetcher_->SetUploadData("application/x-protobuffer", cached_message_);
225 fetcher_->Start();
226 // Clear message to prevent accidentally resending it in the future.
227 cached_message_.clear();
230 void GCMNetworkChannel::OnURLFetchComplete(const net::URLFetcher* source) {
231 DCHECK(CalledOnValidThread());
232 DCHECK_EQ(fetcher_, source);
233 // Free fetcher at the end of function.
234 scoped_ptr<net::URLFetcher> fetcher = fetcher_.Pass();
236 net::URLRequestStatus status = fetcher->GetStatus();
237 diagnostic_info_.last_post_response_code_ =
238 status.is_success() ? source->GetResponseCode() : status.error();
240 if (status.is_success() &&
241 fetcher->GetResponseCode() == net::HTTP_UNAUTHORIZED) {
242 DVLOG(1) << "URLFetcher failure: HTTP_UNAUTHORIZED";
243 delegate_->InvalidateToken(access_token_);
246 if (!status.is_success() ||
247 (fetcher->GetResponseCode() != net::HTTP_OK &&
248 fetcher->GetResponseCode() != net::HTTP_NO_CONTENT)) {
249 DVLOG(1) << "URLFetcher failure";
250 RecordOutgoingMessageStatus(POST_FAILURE);
251 // POST failed. Notify that http channel doesn't work.
252 UpdateHttpChannelState(false);
253 return;
256 RecordOutgoingMessageStatus(OUTGOING_MESSAGE_SUCCESS);
257 // Successfully sent message. Http channel works.
258 UpdateHttpChannelState(true);
259 DVLOG(2) << "URLFetcher success";
262 void GCMNetworkChannel::OnIncomingMessage(const std::string& message,
263 const std::string& echo_token) {
264 #if !defined(OS_ANDROID)
265 if (!echo_token.empty())
266 echo_token_ = echo_token;
267 diagnostic_info_.last_message_empty_echo_token_ = echo_token.empty();
268 diagnostic_info_.last_message_received_time_ = base::Time::Now();
270 if (message.empty()) {
271 RecordIncomingMessageStatus(MESSAGE_EMPTY);
272 return;
274 std::string data;
275 if (!Base64DecodeURLSafe(message, &data)) {
276 RecordIncomingMessageStatus(INVALID_ENCODING);
277 return;
279 ipc::invalidation::AddressedAndroidMessage android_message;
280 if (!android_message.ParseFromString(data) ||
281 !android_message.has_message()) {
282 RecordIncomingMessageStatus(INVALID_PROTO);
283 return;
285 DVLOG(2) << "Deliver incoming message";
286 RecordIncomingMessageStatus(INCOMING_MESSAGE_SUCCESS);
287 UpdateGcmChannelState(true);
288 DeliverIncomingMessage(android_message.message());
289 #else
290 // This code shouldn't be invoked on Android.
291 NOTREACHED();
292 #endif
295 void GCMNetworkChannel::OnConnectionStateChanged(bool online) {
296 UpdateGcmChannelState(online);
299 void GCMNetworkChannel::OnNetworkChanged(
300 net::NetworkChangeNotifier::ConnectionType connection_type) {
301 // Network connection is restored. Let's notify cacheinvalidations so it has
302 // chance to retry.
303 NotifyNetworkStatusChange(
304 connection_type != net::NetworkChangeNotifier::CONNECTION_NONE);
307 void GCMNetworkChannel::UpdateGcmChannelState(bool online) {
308 if (gcm_channel_online_ == online)
309 return;
310 gcm_channel_online_ = online;
311 InvalidatorState channel_state = TRANSIENT_INVALIDATION_ERROR;
312 if (gcm_channel_online_ && http_channel_online_)
313 channel_state = INVALIDATIONS_ENABLED;
314 NotifyChannelStateChange(channel_state);
317 void GCMNetworkChannel::UpdateHttpChannelState(bool online) {
318 if (http_channel_online_ == online)
319 return;
320 http_channel_online_ = online;
321 InvalidatorState channel_state = TRANSIENT_INVALIDATION_ERROR;
322 if (gcm_channel_online_ && http_channel_online_)
323 channel_state = INVALIDATIONS_ENABLED;
324 NotifyChannelStateChange(channel_state);
327 GURL GCMNetworkChannel::BuildUrl(const std::string& registration_id) {
328 DCHECK(!registration_id.empty());
330 #if !defined(OS_ANDROID)
331 ipc::invalidation::EndpointId endpoint_id;
332 endpoint_id.set_c2dm_registration_id(registration_id);
333 endpoint_id.set_client_key(std::string());
334 endpoint_id.set_package_name(kCacheInvalidationPackageName);
335 endpoint_id.mutable_channel_version()->set_major_version(
336 ipc::invalidation::INITIAL);
337 std::string endpoint_id_buffer;
338 endpoint_id.SerializeToString(&endpoint_id_buffer);
340 ipc::invalidation::NetworkEndpointId network_endpoint_id;
341 network_endpoint_id.set_network_address(
342 ipc::invalidation::NetworkEndpointId_NetworkAddress_ANDROID);
343 network_endpoint_id.set_client_address(endpoint_id_buffer);
344 std::string network_endpoint_id_buffer;
345 network_endpoint_id.SerializeToString(&network_endpoint_id_buffer);
347 std::string base64URLPiece;
348 Base64EncodeURLSafe(network_endpoint_id_buffer, &base64URLPiece);
350 std::string url(kCacheInvalidationEndpointUrl);
351 url += base64URLPiece;
352 return GURL(url);
353 #else
354 // This code shouldn't be invoked on Android.
355 NOTREACHED();
356 return GURL();
357 #endif
360 void GCMNetworkChannel::Base64EncodeURLSafe(const std::string& input,
361 std::string* output) {
362 base::Base64Encode(input, output);
363 // Covert to url safe alphabet.
364 base::ReplaceChars(*output, "+", "-", output);
365 base::ReplaceChars(*output, "/", "_", output);
366 // Trim padding.
367 size_t padding_size = 0;
368 for (size_t i = output->size(); i > 0 && (*output)[i - 1] == '='; --i)
369 ++padding_size;
370 output->resize(output->size() - padding_size);
373 bool GCMNetworkChannel::Base64DecodeURLSafe(const std::string& input,
374 std::string* output) {
375 // Add padding.
376 size_t padded_size = (input.size() + 3) - (input.size() + 3) % 4;
377 std::string padded_input(input);
378 padded_input.resize(padded_size, '=');
379 // Convert to standard base64 alphabet.
380 base::ReplaceChars(padded_input, "-", "+", &padded_input);
381 base::ReplaceChars(padded_input, "_", "/", &padded_input);
382 return base::Base64Decode(padded_input, output);
385 void GCMNetworkChannel::SetMessageReceiver(
386 invalidation::MessageCallback* incoming_receiver) {
387 delegate_->SetMessageReceiver(base::Bind(
388 &GCMNetworkChannel::OnIncomingMessage, weak_factory_.GetWeakPtr()));
389 SyncNetworkChannel::SetMessageReceiver(incoming_receiver);
392 void GCMNetworkChannel::RequestDetailedStatus(
393 base::Callback<void(const base::DictionaryValue&)> callback) {
394 callback.Run(*diagnostic_info_.CollectDebugData());
397 void GCMNetworkChannel::UpdateCredentials(const std::string& email,
398 const std::string& token) {
399 // Do nothing. We get access token by requesting it for every message.
402 int GCMNetworkChannel::GetInvalidationClientType() {
403 #if defined(OS_IOS)
404 return ipc::invalidation::ClientType::CHROME_SYNC_GCM_IOS;
405 #else
406 return ipc::invalidation::ClientType::CHROME_SYNC_GCM_DESKTOP;
407 #endif
410 void GCMNetworkChannel::ResetRegisterBackoffEntryForTest(
411 const net::BackoffEntry::Policy* policy) {
412 register_backoff_entry_.reset(new net::BackoffEntry(policy));
415 GCMNetworkChannelDiagnostic::GCMNetworkChannelDiagnostic(
416 GCMNetworkChannel* parent)
417 : parent_(parent),
418 last_message_empty_echo_token_(false),
419 last_post_response_code_(0),
420 registration_result_(gcm::GCMClient::UNKNOWN_ERROR),
421 sent_messages_count_(0) {}
423 scoped_ptr<base::DictionaryValue>
424 GCMNetworkChannelDiagnostic::CollectDebugData() const {
425 scoped_ptr<base::DictionaryValue> status(new base::DictionaryValue);
426 status->SetString("GCMNetworkChannel.Channel", "GCM");
427 std::string reg_id_hash = base::SHA1HashString(registration_id_);
428 status->SetString("GCMNetworkChannel.HashedRegistrationID",
429 base::HexEncode(reg_id_hash.c_str(), reg_id_hash.size()));
430 status->SetString("GCMNetworkChannel.RegistrationResult",
431 GCMClientResultToString(registration_result_));
432 status->SetBoolean("GCMNetworkChannel.HadLastMessageEmptyEchoToken",
433 last_message_empty_echo_token_);
434 status->SetString(
435 "GCMNetworkChannel.LastMessageReceivedTime",
436 base::TimeFormatShortDateAndTime(last_message_received_time_));
437 status->SetInteger("GCMNetworkChannel.LastPostResponseCode",
438 last_post_response_code_);
439 status->SetInteger("GCMNetworkChannel.SentMessages", sent_messages_count_);
440 status->SetInteger("GCMNetworkChannel.ReceivedMessages",
441 parent_->GetReceivedMessagesCount());
442 return status.Pass();
445 std::string GCMNetworkChannelDiagnostic::GCMClientResultToString(
446 const gcm::GCMClient::Result result) const {
447 #define ENUM_CASE(x) case x: return #x; break;
448 switch (result) {
449 ENUM_CASE(gcm::GCMClient::SUCCESS);
450 ENUM_CASE(gcm::GCMClient::NETWORK_ERROR);
451 ENUM_CASE(gcm::GCMClient::SERVER_ERROR);
452 ENUM_CASE(gcm::GCMClient::TTL_EXCEEDED);
453 ENUM_CASE(gcm::GCMClient::UNKNOWN_ERROR);
454 ENUM_CASE(gcm::GCMClient::INVALID_PARAMETER);
455 ENUM_CASE(gcm::GCMClient::ASYNC_OPERATION_PENDING);
456 ENUM_CASE(gcm::GCMClient::GCM_DISABLED);
458 NOTREACHED();
459 return "";
462 } // namespace syncer