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