Disable UserAddingScreenTest.AddingSeveralUsers, very flaky.
[chromium-blink-merge.git] / sync / notifier / gcm_network_channel.cc
blob484ea057c855740ed1ee7fe43463f06a0455d726
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 #endif
18 #include "google_apis/gaia/google_service_auth_error.h"
19 #include "net/http/http_status_code.h"
20 #include "net/url_request/url_fetcher.h"
21 #include "net/url_request/url_request_status.h"
22 #include "sync/notifier/gcm_network_channel.h"
23 #include "sync/notifier/gcm_network_channel_delegate.h"
25 namespace syncer {
27 namespace {
29 const char kCacheInvalidationEndpointUrl[] =
30 "https://clients4.google.com/invalidation/android/request/";
31 const char kCacheInvalidationPackageName[] = "com.google.chrome.invalidations";
33 // Register backoff policy.
34 const net::BackoffEntry::Policy kRegisterBackoffPolicy = {
35 // Number of initial errors (in sequence) to ignore before applying
36 // exponential back-off rules.
39 // Initial delay for exponential back-off in ms.
40 2000, // 2 seconds.
42 // Factor by which the waiting time will be multiplied.
45 // Fuzzing percentage. ex: 10% will spread requests randomly
46 // between 90%-100% of the calculated time.
47 0.2, // 20%.
49 // Maximum amount of time we are willing to delay our request in ms.
50 1000 * 3600 * 4, // 4 hours.
52 // Time to keep an entry from being discarded even when it
53 // has no significant state, -1 to never discard.
54 -1,
56 // Don't use initial delay unless the last request was an error.
57 false,
60 // Incoming message status values for UMA_HISTOGRAM.
61 enum IncomingMessageStatus {
62 INCOMING_MESSAGE_SUCCESS,
63 MESSAGE_EMPTY, // GCM message's content is missing or empty.
64 INVALID_ENCODING, // Base64Decode failed.
65 INVALID_PROTO, // Parsing protobuf failed.
67 // This enum is used in UMA_HISTOGRAM_ENUMERATION. Insert new values above
68 // this line.
69 INCOMING_MESSAGE_STATUS_COUNT
72 // Outgoing message status values for UMA_HISTOGRAM.
73 enum OutgoingMessageStatus {
74 OUTGOING_MESSAGE_SUCCESS,
75 MESSAGE_DISCARDED, // New message started before old one was sent.
76 ACCESS_TOKEN_FAILURE, // Requeting access token failed.
77 POST_FAILURE, // HTTP Post failed.
79 // This enum is used in UMA_HISTOGRAM_ENUMERATION. Insert new values above
80 // this line.
81 OUTGOING_MESSAGE_STATUS_COUNT
84 const char kIncomingMessageStatusHistogram[] =
85 "GCMInvalidations.IncomingMessageStatus";
86 const char kOutgoingMessageStatusHistogram[] =
87 "GCMInvalidations.OutgoingMessageStatus";
89 void RecordIncomingMessageStatus(IncomingMessageStatus status) {
90 UMA_HISTOGRAM_ENUMERATION(kIncomingMessageStatusHistogram,
91 status,
92 INCOMING_MESSAGE_STATUS_COUNT);
95 void RecordOutgoingMessageStatus(OutgoingMessageStatus status) {
96 UMA_HISTOGRAM_ENUMERATION(kOutgoingMessageStatusHistogram,
97 MESSAGE_DISCARDED,
98 OUTGOING_MESSAGE_STATUS_COUNT);
101 } // namespace
103 GCMNetworkChannel::GCMNetworkChannel(
104 scoped_refptr<net::URLRequestContextGetter> request_context_getter,
105 scoped_ptr<GCMNetworkChannelDelegate> delegate)
106 : request_context_getter_(request_context_getter),
107 delegate_(delegate.Pass()),
108 register_backoff_entry_(new net::BackoffEntry(&kRegisterBackoffPolicy)),
109 diagnostic_info_(this),
110 weak_factory_(this) {
111 net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
112 delegate_->Initialize();
113 Register();
116 GCMNetworkChannel::~GCMNetworkChannel() {
117 net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
120 void GCMNetworkChannel::Register() {
121 delegate_->Register(base::Bind(&GCMNetworkChannel::OnRegisterComplete,
122 weak_factory_.GetWeakPtr()));
125 void GCMNetworkChannel::OnRegisterComplete(
126 const std::string& registration_id,
127 gcm::GCMClient::Result result) {
128 DCHECK(CalledOnValidThread());
129 if (result == gcm::GCMClient::SUCCESS) {
130 DCHECK(!registration_id.empty());
131 DVLOG(2) << "Got registration_id";
132 register_backoff_entry_->Reset();
133 registration_id_ = registration_id;
134 if (!cached_message_.empty())
135 RequestAccessToken();
136 } else {
137 DVLOG(2) << "Register failed: " << result;
138 // Retry in case of transient error.
139 switch (result) {
140 case gcm::GCMClient::NETWORK_ERROR:
141 case gcm::GCMClient::SERVER_ERROR:
142 case gcm::GCMClient::TTL_EXCEEDED:
143 case gcm::GCMClient::UNKNOWN_ERROR: {
144 register_backoff_entry_->InformOfRequest(false);
145 base::MessageLoop::current()->PostDelayedTask(
146 FROM_HERE,
147 base::Bind(&GCMNetworkChannel::Register,
148 weak_factory_.GetWeakPtr()),
149 register_backoff_entry_->GetTimeUntilRelease());
150 break;
152 default:
153 break;
156 diagnostic_info_.registration_id_ = registration_id_;
157 diagnostic_info_.registration_result_ = result;
160 void GCMNetworkChannel::SendMessage(const std::string& message) {
161 DCHECK(CalledOnValidThread());
162 DCHECK(!message.empty());
163 DVLOG(2) << "SendMessage";
164 diagnostic_info_.sent_messages_count_++;
165 if (!cached_message_.empty()) {
166 RecordOutgoingMessageStatus(MESSAGE_DISCARDED);
168 cached_message_ = message;
170 if (!registration_id_.empty()) {
171 RequestAccessToken();
175 void GCMNetworkChannel::RequestAccessToken() {
176 DCHECK(CalledOnValidThread());
177 delegate_->RequestToken(base::Bind(&GCMNetworkChannel::OnGetTokenComplete,
178 weak_factory_.GetWeakPtr()));
181 void GCMNetworkChannel::OnGetTokenComplete(
182 const GoogleServiceAuthError& error,
183 const std::string& token) {
184 DCHECK(CalledOnValidThread());
185 if (cached_message_.empty()) {
186 // Nothing to do.
187 return;
190 if (error.state() != GoogleServiceAuthError::NONE) {
191 // Requesting access token failed. Persistent errors will be reported by
192 // token service. Just drop this request, cacheinvalidations will retry
193 // sending message and at that time we'll retry requesting access token.
194 DVLOG(1) << "RequestAccessToken failed: " << error.ToString();
195 RecordOutgoingMessageStatus(ACCESS_TOKEN_FAILURE);
196 // Message won't get sent because of connection failure. Let's retry once
197 // connection is restored.
198 if (error.state() == GoogleServiceAuthError::CONNECTION_FAILED)
199 NotifyStateChange(TRANSIENT_INVALIDATION_ERROR);
200 cached_message_.clear();
201 return;
203 DCHECK(!token.empty());
204 // Save access token in case POST fails and we need to invalidate it.
205 access_token_ = token;
207 DVLOG(2) << "Got access token, sending message";
208 fetcher_.reset(net::URLFetcher::Create(
209 BuildUrl(registration_id_), net::URLFetcher::POST, this));
210 fetcher_->SetRequestContext(request_context_getter_);
211 const std::string auth_header("Authorization: Bearer " + access_token_);
212 fetcher_->AddExtraRequestHeader(auth_header);
213 if (!echo_token_.empty()) {
214 const std::string echo_header("echo-token: " + echo_token_);
215 fetcher_->AddExtraRequestHeader(echo_header);
217 fetcher_->SetUploadData("application/x-protobuffer", cached_message_);
218 fetcher_->Start();
219 // Clear message to prevent accidentally resending it in the future.
220 cached_message_.clear();
223 void GCMNetworkChannel::OnURLFetchComplete(const net::URLFetcher* source) {
224 DCHECK(CalledOnValidThread());
225 DCHECK_EQ(fetcher_, source);
226 // Free fetcher at the end of function.
227 scoped_ptr<net::URLFetcher> fetcher = fetcher_.Pass();
229 net::URLRequestStatus status = fetcher->GetStatus();
230 diagnostic_info_.last_post_response_code_ =
231 status.is_success() ? source->GetResponseCode() : status.error();
233 if (status.is_success() &&
234 fetcher->GetResponseCode() == net::HTTP_UNAUTHORIZED) {
235 DVLOG(1) << "URLFetcher failure: HTTP_UNAUTHORIZED";
236 delegate_->InvalidateToken(access_token_);
239 if (!status.is_success() ||
240 (fetcher->GetResponseCode() != net::HTTP_OK &&
241 fetcher->GetResponseCode() != net::HTTP_NO_CONTENT)) {
242 DVLOG(1) << "URLFetcher failure";
243 RecordOutgoingMessageStatus(POST_FAILURE);
244 NotifyStateChange(TRANSIENT_INVALIDATION_ERROR);
245 return;
248 RecordOutgoingMessageStatus(OUTGOING_MESSAGE_SUCCESS);
249 NotifyStateChange(INVALIDATIONS_ENABLED);
250 DVLOG(2) << "URLFetcher success";
253 void GCMNetworkChannel::OnIncomingMessage(const std::string& message,
254 const std::string& echo_token) {
255 #if !defined(OS_ANDROID)
256 if (!echo_token.empty())
257 echo_token_ = echo_token;
258 diagnostic_info_.last_message_empty_echo_token_ = echo_token.empty();
259 diagnostic_info_.last_message_received_time_ = base::Time::Now();
261 if (message.empty()) {
262 RecordIncomingMessageStatus(MESSAGE_EMPTY);
263 return;
265 std::string data;
266 if (!Base64DecodeURLSafe(message, &data)) {
267 RecordIncomingMessageStatus(INVALID_ENCODING);
268 return;
270 ipc::invalidation::AddressedAndroidMessage android_message;
271 if (!android_message.ParseFromString(data) ||
272 !android_message.has_message()) {
273 RecordIncomingMessageStatus(INVALID_PROTO);
274 return;
276 DVLOG(2) << "Deliver incoming message";
277 RecordIncomingMessageStatus(INCOMING_MESSAGE_SUCCESS);
278 DeliverIncomingMessage(android_message.message());
279 #else
280 // This code shouldn't be invoked on Android.
281 NOTREACHED();
282 #endif
285 void GCMNetworkChannel::OnNetworkChanged(
286 net::NetworkChangeNotifier::ConnectionType connection_type) {
287 // Network connection is restored. Let's notify cacheinvalidations so it has
288 // chance to retry.
289 if (connection_type != net::NetworkChangeNotifier::CONNECTION_NONE)
290 NotifyStateChange(INVALIDATIONS_ENABLED);
293 GURL GCMNetworkChannel::BuildUrl(const std::string& registration_id) {
294 DCHECK(!registration_id.empty());
296 #if !defined(OS_ANDROID)
297 ipc::invalidation::EndpointId endpoint_id;
298 endpoint_id.set_c2dm_registration_id(registration_id);
299 endpoint_id.set_client_key(std::string());
300 endpoint_id.set_package_name(kCacheInvalidationPackageName);
301 endpoint_id.mutable_channel_version()->set_major_version(
302 ipc::invalidation::INITIAL);
303 std::string endpoint_id_buffer;
304 endpoint_id.SerializeToString(&endpoint_id_buffer);
306 ipc::invalidation::NetworkEndpointId network_endpoint_id;
307 network_endpoint_id.set_network_address(
308 ipc::invalidation::NetworkEndpointId_NetworkAddress_ANDROID);
309 network_endpoint_id.set_client_address(endpoint_id_buffer);
310 std::string network_endpoint_id_buffer;
311 network_endpoint_id.SerializeToString(&network_endpoint_id_buffer);
313 std::string base64URLPiece;
314 Base64EncodeURLSafe(network_endpoint_id_buffer, &base64URLPiece);
316 std::string url(kCacheInvalidationEndpointUrl);
317 url += base64URLPiece;
318 return GURL(url);
319 #else
320 // This code shouldn't be invoked on Android.
321 NOTREACHED();
322 return GURL();
323 #endif
326 void GCMNetworkChannel::Base64EncodeURLSafe(const std::string& input,
327 std::string* output) {
328 base::Base64Encode(input, output);
329 // Covert to url safe alphabet.
330 base::ReplaceChars(*output, "+", "-", output);
331 base::ReplaceChars(*output, "/", "_", output);
332 // Trim padding.
333 size_t padding_size = 0;
334 for (size_t i = output->size(); i > 0 && (*output)[i - 1] == '='; --i)
335 ++padding_size;
336 output->resize(output->size() - padding_size);
339 bool GCMNetworkChannel::Base64DecodeURLSafe(const std::string& input,
340 std::string* output) {
341 // Add padding.
342 size_t padded_size = (input.size() + 3) - (input.size() + 3) % 4;
343 std::string padded_input(input);
344 padded_input.resize(padded_size, '=');
345 // Convert to standard base64 alphabet.
346 base::ReplaceChars(padded_input, "-", "+", &padded_input);
347 base::ReplaceChars(padded_input, "_", "/", &padded_input);
348 return base::Base64Decode(padded_input, output);
351 void GCMNetworkChannel::SetMessageReceiver(
352 invalidation::MessageCallback* incoming_receiver) {
353 delegate_->SetMessageReceiver(base::Bind(
354 &GCMNetworkChannel::OnIncomingMessage, weak_factory_.GetWeakPtr()));
355 SyncNetworkChannel::SetMessageReceiver(incoming_receiver);
358 void GCMNetworkChannel::RequestDetailedStatus(
359 base::Callback<void(const base::DictionaryValue&)> callback) {
360 callback.Run(*diagnostic_info_.CollectDebugData());
363 void GCMNetworkChannel::UpdateCredentials(const std::string& email,
364 const std::string& token) {
365 // Do nothing. We get access token by requesting it for every message.
368 void GCMNetworkChannel::ResetRegisterBackoffEntryForTest(
369 const net::BackoffEntry::Policy* policy) {
370 register_backoff_entry_.reset(new net::BackoffEntry(policy));
373 GCMNetworkChannelDiagnostic::GCMNetworkChannelDiagnostic(
374 GCMNetworkChannel* parent)
375 : parent_(parent),
376 last_message_empty_echo_token_(false),
377 last_post_response_code_(0),
378 registration_result_(gcm::GCMClient::UNKNOWN_ERROR),
379 sent_messages_count_(0) {}
381 scoped_ptr<base::DictionaryValue>
382 GCMNetworkChannelDiagnostic::CollectDebugData() const {
383 scoped_ptr<base::DictionaryValue> status(new base::DictionaryValue);
384 status->SetString("GCMNetworkChannel.Channel", "GCM");
385 std::string reg_id_hash = base::SHA1HashString(registration_id_);
386 status->SetString("GCMNetworkChannel.HashedRegistrationID",
387 base::HexEncode(reg_id_hash.c_str(), reg_id_hash.size()));
388 status->SetString("GCMNetworkChannel.RegistrationResult",
389 GCMClientResultToString(registration_result_));
390 status->SetBoolean("GCMNetworkChannel.HadLastMessageEmptyEchoToken",
391 last_message_empty_echo_token_);
392 status->SetString(
393 "GCMNetworkChannel.LastMessageReceivedTime",
394 base::TimeFormatShortDateAndTime(last_message_received_time_));
395 status->SetInteger("GCMNetworkChannel.LastPostResponseCode",
396 last_post_response_code_);
397 status->SetInteger("GCMNetworkChannel.SentMessages", sent_messages_count_);
398 status->SetInteger("GCMNetworkChannel.ReceivedMessages",
399 parent_->GetReceivedMessagesCount());
400 return status.Pass();
403 std::string GCMNetworkChannelDiagnostic::GCMClientResultToString(
404 const gcm::GCMClient::Result result) const {
405 #define ENUM_CASE(x) case x: return #x; break;
406 switch (result) {
407 ENUM_CASE(gcm::GCMClient::SUCCESS);
408 ENUM_CASE(gcm::GCMClient::NETWORK_ERROR);
409 ENUM_CASE(gcm::GCMClient::SERVER_ERROR);
410 ENUM_CASE(gcm::GCMClient::TTL_EXCEEDED);
411 ENUM_CASE(gcm::GCMClient::UNKNOWN_ERROR);
412 ENUM_CASE(gcm::GCMClient::NOT_SIGNED_IN);
413 ENUM_CASE(gcm::GCMClient::INVALID_PARAMETER);
414 ENUM_CASE(gcm::GCMClient::ASYNC_OPERATION_PENDING);
416 NOTREACHED();
417 return "";
420 } // namespace syncer