Forward accessibility events to the automation extension process.
[chromium-blink-merge.git] / components / invalidation / gcm_network_channel.cc
blob9f74b551c11f68df2f9c17588af0af525a6bee17
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.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/invalidation/gcm_network_channel.h"
23 #include "components/invalidation/gcm_network_channel_delegate.h"
24 #include "google_apis/gaia/google_service_auth_error.h"
25 #include "net/http/http_status_code.h"
26 #include "net/url_request/url_fetcher.h"
27 #include "net/url_request/url_request_status.h"
29 namespace syncer {
31 namespace {
33 const char kCacheInvalidationEndpointUrl[] =
34 "https://clients4.google.com/invalidation/android/request/";
35 const char kCacheInvalidationPackageName[] = "com.google.chrome.invalidations";
37 // Register backoff policy.
38 const net::BackoffEntry::Policy kRegisterBackoffPolicy = {
39 // Number of initial errors (in sequence) to ignore before applying
40 // exponential back-off rules.
43 // Initial delay for exponential back-off in ms.
44 2000, // 2 seconds.
46 // Factor by which the waiting time will be multiplied.
49 // Fuzzing percentage. ex: 10% will spread requests randomly
50 // between 90%-100% of the calculated time.
51 0.2, // 20%.
53 // Maximum amount of time we are willing to delay our request in ms.
54 1000 * 3600 * 4, // 4 hours.
56 // Time to keep an entry from being discarded even when it
57 // has no significant state, -1 to never discard.
58 -1,
60 // Don't use initial delay unless the last request was an error.
61 false,
64 // Incoming message status values for UMA_HISTOGRAM.
65 enum IncomingMessageStatus {
66 INCOMING_MESSAGE_SUCCESS,
67 MESSAGE_EMPTY, // GCM message's content is missing or empty.
68 INVALID_ENCODING, // Base64Decode failed.
69 INVALID_PROTO, // Parsing protobuf failed.
71 // This enum is used in UMA_HISTOGRAM_ENUMERATION. Insert new values above
72 // this line.
73 INCOMING_MESSAGE_STATUS_COUNT
76 // Outgoing message status values for UMA_HISTOGRAM.
77 enum OutgoingMessageStatus {
78 OUTGOING_MESSAGE_SUCCESS,
79 MESSAGE_DISCARDED, // New message started before old one was sent.
80 ACCESS_TOKEN_FAILURE, // Requeting access token failed.
81 POST_FAILURE, // HTTP Post failed.
83 // This enum is used in UMA_HISTOGRAM_ENUMERATION. Insert new values above
84 // this line.
85 OUTGOING_MESSAGE_STATUS_COUNT
88 const char kIncomingMessageStatusHistogram[] =
89 "GCMInvalidations.IncomingMessageStatus";
90 const char kOutgoingMessageStatusHistogram[] =
91 "GCMInvalidations.OutgoingMessageStatus";
93 void RecordIncomingMessageStatus(IncomingMessageStatus status) {
94 UMA_HISTOGRAM_ENUMERATION(kIncomingMessageStatusHistogram,
95 status,
96 INCOMING_MESSAGE_STATUS_COUNT);
99 void RecordOutgoingMessageStatus(OutgoingMessageStatus status) {
100 UMA_HISTOGRAM_ENUMERATION(kOutgoingMessageStatusHistogram,
101 status,
102 OUTGOING_MESSAGE_STATUS_COUNT);
105 } // namespace
107 GCMNetworkChannel::GCMNetworkChannel(
108 scoped_refptr<net::URLRequestContextGetter> request_context_getter,
109 scoped_ptr<GCMNetworkChannelDelegate> delegate)
110 : request_context_getter_(request_context_getter),
111 delegate_(delegate.Pass()),
112 register_backoff_entry_(new net::BackoffEntry(&kRegisterBackoffPolicy)),
113 gcm_channel_online_(false),
114 http_channel_online_(false),
115 diagnostic_info_(this),
116 weak_factory_(this) {
117 net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
118 delegate_->Initialize(base::Bind(&GCMNetworkChannel::OnConnectionStateChanged,
119 weak_factory_.GetWeakPtr()));
120 Register();
123 GCMNetworkChannel::~GCMNetworkChannel() {
124 net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
127 void GCMNetworkChannel::Register() {
128 delegate_->Register(base::Bind(&GCMNetworkChannel::OnRegisterComplete,
129 weak_factory_.GetWeakPtr()));
132 void GCMNetworkChannel::OnRegisterComplete(
133 const std::string& registration_id,
134 gcm::GCMClient::Result result) {
135 DCHECK(CalledOnValidThread());
136 if (result == gcm::GCMClient::SUCCESS) {
137 DCHECK(!registration_id.empty());
138 DVLOG(2) << "Got registration_id";
139 register_backoff_entry_->Reset();
140 registration_id_ = registration_id;
141 if (!cached_message_.empty())
142 RequestAccessToken();
143 } else {
144 DVLOG(2) << "Register failed: " << result;
145 // Retry in case of transient error.
146 switch (result) {
147 case gcm::GCMClient::NETWORK_ERROR:
148 case gcm::GCMClient::SERVER_ERROR:
149 case gcm::GCMClient::TTL_EXCEEDED:
150 case gcm::GCMClient::UNKNOWN_ERROR: {
151 register_backoff_entry_->InformOfRequest(false);
152 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
153 FROM_HERE, base::Bind(&GCMNetworkChannel::Register,
154 weak_factory_.GetWeakPtr()),
155 register_backoff_entry_->GetTimeUntilRelease());
156 break;
158 default:
159 break;
162 diagnostic_info_.registration_id_ = registration_id_;
163 diagnostic_info_.registration_result_ = result;
166 void GCMNetworkChannel::SendMessage(const std::string& message) {
167 DCHECK(CalledOnValidThread());
168 DCHECK(!message.empty());
169 DVLOG(2) << "SendMessage";
170 diagnostic_info_.sent_messages_count_++;
171 if (!cached_message_.empty()) {
172 RecordOutgoingMessageStatus(MESSAGE_DISCARDED);
174 cached_message_ = message;
176 if (!registration_id_.empty()) {
177 RequestAccessToken();
181 void GCMNetworkChannel::RequestAccessToken() {
182 DCHECK(CalledOnValidThread());
183 delegate_->RequestToken(base::Bind(&GCMNetworkChannel::OnGetTokenComplete,
184 weak_factory_.GetWeakPtr()));
187 void GCMNetworkChannel::OnGetTokenComplete(
188 const GoogleServiceAuthError& error,
189 const std::string& token) {
190 DCHECK(CalledOnValidThread());
191 if (cached_message_.empty()) {
192 // Nothing to do.
193 return;
196 if (error.state() != GoogleServiceAuthError::NONE) {
197 // Requesting access token failed. Persistent errors will be reported by
198 // token service. Just drop this request, cacheinvalidations will retry
199 // sending message and at that time we'll retry requesting access token.
200 DVLOG(1) << "RequestAccessToken failed: " << error.ToString();
201 RecordOutgoingMessageStatus(ACCESS_TOKEN_FAILURE);
202 // Message won't get sent. Notify that http channel doesn't work.
203 UpdateHttpChannelState(false);
204 cached_message_.clear();
205 return;
207 DCHECK(!token.empty());
208 // Save access token in case POST fails and we need to invalidate it.
209 access_token_ = token;
211 DVLOG(2) << "Got access token, sending message";
212 fetcher_ = net::URLFetcher::Create(BuildUrl(registration_id_),
213 net::URLFetcher::POST, this);
214 fetcher_->SetRequestContext(request_context_getter_.get());
215 const std::string auth_header("Authorization: Bearer " + access_token_);
216 fetcher_->AddExtraRequestHeader(auth_header);
217 if (!echo_token_.empty()) {
218 const std::string echo_header("echo-token: " + echo_token_);
219 fetcher_->AddExtraRequestHeader(echo_header);
221 fetcher_->SetUploadData("application/x-protobuffer", cached_message_);
222 fetcher_->Start();
223 // Clear message to prevent accidentally resending it in the future.
224 cached_message_.clear();
227 void GCMNetworkChannel::OnURLFetchComplete(const net::URLFetcher* source) {
228 DCHECK(CalledOnValidThread());
229 DCHECK_EQ(fetcher_, source);
230 // Free fetcher at the end of function.
231 scoped_ptr<net::URLFetcher> fetcher = fetcher_.Pass();
233 net::URLRequestStatus status = fetcher->GetStatus();
234 diagnostic_info_.last_post_response_code_ =
235 status.is_success() ? source->GetResponseCode() : status.error();
237 if (status.is_success() &&
238 fetcher->GetResponseCode() == net::HTTP_UNAUTHORIZED) {
239 DVLOG(1) << "URLFetcher failure: HTTP_UNAUTHORIZED";
240 delegate_->InvalidateToken(access_token_);
243 if (!status.is_success() ||
244 (fetcher->GetResponseCode() != net::HTTP_OK &&
245 fetcher->GetResponseCode() != net::HTTP_NO_CONTENT)) {
246 DVLOG(1) << "URLFetcher failure";
247 RecordOutgoingMessageStatus(POST_FAILURE);
248 // POST failed. Notify that http channel doesn't work.
249 UpdateHttpChannelState(false);
250 return;
253 RecordOutgoingMessageStatus(OUTGOING_MESSAGE_SUCCESS);
254 // Successfully sent message. Http channel works.
255 UpdateHttpChannelState(true);
256 DVLOG(2) << "URLFetcher success";
259 void GCMNetworkChannel::OnIncomingMessage(const std::string& message,
260 const std::string& echo_token) {
261 #if !defined(OS_ANDROID)
262 if (!echo_token.empty())
263 echo_token_ = echo_token;
264 diagnostic_info_.last_message_empty_echo_token_ = echo_token.empty();
265 diagnostic_info_.last_message_received_time_ = base::Time::Now();
267 if (message.empty()) {
268 RecordIncomingMessageStatus(MESSAGE_EMPTY);
269 return;
271 std::string data;
272 if (!Base64DecodeURLSafe(message, &data)) {
273 RecordIncomingMessageStatus(INVALID_ENCODING);
274 return;
276 ipc::invalidation::AddressedAndroidMessage android_message;
277 if (!android_message.ParseFromString(data) ||
278 !android_message.has_message()) {
279 RecordIncomingMessageStatus(INVALID_PROTO);
280 return;
282 DVLOG(2) << "Deliver incoming message";
283 RecordIncomingMessageStatus(INCOMING_MESSAGE_SUCCESS);
284 UpdateGcmChannelState(true);
285 DeliverIncomingMessage(android_message.message());
286 #else
287 // This code shouldn't be invoked on Android.
288 NOTREACHED();
289 #endif
292 void GCMNetworkChannel::OnConnectionStateChanged(bool online) {
293 UpdateGcmChannelState(online);
296 void GCMNetworkChannel::OnNetworkChanged(
297 net::NetworkChangeNotifier::ConnectionType connection_type) {
298 // Network connection is restored. Let's notify cacheinvalidations so it has
299 // chance to retry.
300 NotifyNetworkStatusChange(
301 connection_type != net::NetworkChangeNotifier::CONNECTION_NONE);
304 void GCMNetworkChannel::UpdateGcmChannelState(bool online) {
305 if (gcm_channel_online_ == online)
306 return;
307 gcm_channel_online_ = online;
308 InvalidatorState channel_state = TRANSIENT_INVALIDATION_ERROR;
309 if (gcm_channel_online_ && http_channel_online_)
310 channel_state = INVALIDATIONS_ENABLED;
311 NotifyChannelStateChange(channel_state);
314 void GCMNetworkChannel::UpdateHttpChannelState(bool online) {
315 if (http_channel_online_ == online)
316 return;
317 http_channel_online_ = online;
318 InvalidatorState channel_state = TRANSIENT_INVALIDATION_ERROR;
319 if (gcm_channel_online_ && http_channel_online_)
320 channel_state = INVALIDATIONS_ENABLED;
321 NotifyChannelStateChange(channel_state);
324 GURL GCMNetworkChannel::BuildUrl(const std::string& registration_id) {
325 DCHECK(!registration_id.empty());
327 #if !defined(OS_ANDROID)
328 ipc::invalidation::EndpointId endpoint_id;
329 endpoint_id.set_c2dm_registration_id(registration_id);
330 endpoint_id.set_client_key(std::string());
331 endpoint_id.set_package_name(kCacheInvalidationPackageName);
332 endpoint_id.mutable_channel_version()->set_major_version(
333 ipc::invalidation::INITIAL);
334 std::string endpoint_id_buffer;
335 endpoint_id.SerializeToString(&endpoint_id_buffer);
337 ipc::invalidation::NetworkEndpointId network_endpoint_id;
338 network_endpoint_id.set_network_address(
339 ipc::invalidation::NetworkEndpointId_NetworkAddress_ANDROID);
340 network_endpoint_id.set_client_address(endpoint_id_buffer);
341 std::string network_endpoint_id_buffer;
342 network_endpoint_id.SerializeToString(&network_endpoint_id_buffer);
344 std::string base64URLPiece;
345 Base64EncodeURLSafe(network_endpoint_id_buffer, &base64URLPiece);
347 std::string url(kCacheInvalidationEndpointUrl);
348 url += base64URLPiece;
349 return GURL(url);
350 #else
351 // This code shouldn't be invoked on Android.
352 NOTREACHED();
353 return GURL();
354 #endif
357 void GCMNetworkChannel::Base64EncodeURLSafe(const std::string& input,
358 std::string* output) {
359 base::Base64Encode(input, output);
360 // Covert to url safe alphabet.
361 base::ReplaceChars(*output, "+", "-", output);
362 base::ReplaceChars(*output, "/", "_", output);
363 // Trim padding.
364 size_t padding_size = 0;
365 for (size_t i = output->size(); i > 0 && (*output)[i - 1] == '='; --i)
366 ++padding_size;
367 output->resize(output->size() - padding_size);
370 bool GCMNetworkChannel::Base64DecodeURLSafe(const std::string& input,
371 std::string* output) {
372 // Add padding.
373 size_t padded_size = (input.size() + 3) - (input.size() + 3) % 4;
374 std::string padded_input(input);
375 padded_input.resize(padded_size, '=');
376 // Convert to standard base64 alphabet.
377 base::ReplaceChars(padded_input, "-", "+", &padded_input);
378 base::ReplaceChars(padded_input, "_", "/", &padded_input);
379 return base::Base64Decode(padded_input, output);
382 void GCMNetworkChannel::SetMessageReceiver(
383 invalidation::MessageCallback* incoming_receiver) {
384 delegate_->SetMessageReceiver(base::Bind(
385 &GCMNetworkChannel::OnIncomingMessage, weak_factory_.GetWeakPtr()));
386 SyncNetworkChannel::SetMessageReceiver(incoming_receiver);
389 void GCMNetworkChannel::RequestDetailedStatus(
390 base::Callback<void(const base::DictionaryValue&)> callback) {
391 callback.Run(*diagnostic_info_.CollectDebugData());
394 void GCMNetworkChannel::UpdateCredentials(const std::string& email,
395 const std::string& token) {
396 // Do nothing. We get access token by requesting it for every message.
399 int GCMNetworkChannel::GetInvalidationClientType() {
400 #if defined(OS_IOS)
401 return ipc::invalidation::ClientType::CHROME_SYNC_GCM_IOS;
402 #else
403 return ipc::invalidation::ClientType::CHROME_SYNC_GCM_DESKTOP;
404 #endif
407 void GCMNetworkChannel::ResetRegisterBackoffEntryForTest(
408 const net::BackoffEntry::Policy* policy) {
409 register_backoff_entry_.reset(new net::BackoffEntry(policy));
412 GCMNetworkChannelDiagnostic::GCMNetworkChannelDiagnostic(
413 GCMNetworkChannel* parent)
414 : parent_(parent),
415 last_message_empty_echo_token_(false),
416 last_post_response_code_(0),
417 registration_result_(gcm::GCMClient::UNKNOWN_ERROR),
418 sent_messages_count_(0) {}
420 scoped_ptr<base::DictionaryValue>
421 GCMNetworkChannelDiagnostic::CollectDebugData() const {
422 scoped_ptr<base::DictionaryValue> status(new base::DictionaryValue);
423 status->SetString("GCMNetworkChannel.Channel", "GCM");
424 std::string reg_id_hash = base::SHA1HashString(registration_id_);
425 status->SetString("GCMNetworkChannel.HashedRegistrationID",
426 base::HexEncode(reg_id_hash.c_str(), reg_id_hash.size()));
427 status->SetString("GCMNetworkChannel.RegistrationResult",
428 GCMClientResultToString(registration_result_));
429 status->SetBoolean("GCMNetworkChannel.HadLastMessageEmptyEchoToken",
430 last_message_empty_echo_token_);
431 status->SetString(
432 "GCMNetworkChannel.LastMessageReceivedTime",
433 base::TimeFormatShortDateAndTime(last_message_received_time_));
434 status->SetInteger("GCMNetworkChannel.LastPostResponseCode",
435 last_post_response_code_);
436 status->SetInteger("GCMNetworkChannel.SentMessages", sent_messages_count_);
437 status->SetInteger("GCMNetworkChannel.ReceivedMessages",
438 parent_->GetReceivedMessagesCount());
439 return status.Pass();
442 std::string GCMNetworkChannelDiagnostic::GCMClientResultToString(
443 const gcm::GCMClient::Result result) const {
444 #define ENUM_CASE(x) case x: return #x; break;
445 switch (result) {
446 ENUM_CASE(gcm::GCMClient::SUCCESS);
447 ENUM_CASE(gcm::GCMClient::NETWORK_ERROR);
448 ENUM_CASE(gcm::GCMClient::SERVER_ERROR);
449 ENUM_CASE(gcm::GCMClient::TTL_EXCEEDED);
450 ENUM_CASE(gcm::GCMClient::UNKNOWN_ERROR);
451 ENUM_CASE(gcm::GCMClient::INVALID_PARAMETER);
452 ENUM_CASE(gcm::GCMClient::ASYNC_OPERATION_PENDING);
453 ENUM_CASE(gcm::GCMClient::GCM_DISABLED);
455 NOTREACHED();
456 return "";
459 } // namespace syncer