Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / components / invalidation / gcm_network_channel.cc
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.
4
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"
25
26 namespace syncer {
27
28 namespace {
29
30 const char kCacheInvalidationEndpointUrl[] =
31     "https://clients4.google.com/invalidation/android/request/";
32 const char kCacheInvalidationPackageName[] = "com.google.chrome.invalidations";
33
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.
38   0,
39
40   // Initial delay for exponential back-off in ms.
41   2000, // 2 seconds.
42
43   // Factor by which the waiting time will be multiplied.
44   2,
45
46   // Fuzzing percentage. ex: 10% will spread requests randomly
47   // between 90%-100% of the calculated time.
48   0.2, // 20%.
49
50   // Maximum amount of time we are willing to delay our request in ms.
51   1000 * 3600 * 4, // 4 hours.
52
53   // Time to keep an entry from being discarded even when it
54   // has no significant state, -1 to never discard.
55   -1,
56
57   // Don't use initial delay unless the last request was an error.
58   false,
59 };
60
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.
67
68   // This enum is used in UMA_HISTOGRAM_ENUMERATION. Insert new values above
69   // this line.
70   INCOMING_MESSAGE_STATUS_COUNT
71 };
72
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.
79
80   // This enum is used in UMA_HISTOGRAM_ENUMERATION. Insert new values above
81   // this line.
82   OUTGOING_MESSAGE_STATUS_COUNT
83 };
84
85 const char kIncomingMessageStatusHistogram[] =
86     "GCMInvalidations.IncomingMessageStatus";
87 const char kOutgoingMessageStatusHistogram[] =
88     "GCMInvalidations.OutgoingMessageStatus";
89
90 void RecordIncomingMessageStatus(IncomingMessageStatus status) {
91   UMA_HISTOGRAM_ENUMERATION(kIncomingMessageStatusHistogram,
92                             status,
93                             INCOMING_MESSAGE_STATUS_COUNT);
94 }
95
96 void RecordOutgoingMessageStatus(OutgoingMessageStatus status) {
97   UMA_HISTOGRAM_ENUMERATION(kOutgoingMessageStatusHistogram,
98                             status,
99                             OUTGOING_MESSAGE_STATUS_COUNT);
100 }
101
102 }  // namespace
103
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();
118 }
119
120 GCMNetworkChannel::~GCMNetworkChannel() {
121   net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
122 }
123
124 void GCMNetworkChannel::Register() {
125   delegate_->Register(base::Bind(&GCMNetworkChannel::OnRegisterComplete,
126                                  weak_factory_.GetWeakPtr()));
127 }
128
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;
155       }
156       default:
157         break;
158     }
159   }
160   diagnostic_info_.registration_id_ = registration_id_;
161   diagnostic_info_.registration_result_ = result;
162 }
163
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);
171   }
172   cached_message_ = message;
173
174   if (!registration_id_.empty()) {
175     RequestAccessToken();
176   }
177 }
178
179 void GCMNetworkChannel::RequestAccessToken() {
180   DCHECK(CalledOnValidThread());
181   delegate_->RequestToken(base::Bind(&GCMNetworkChannel::OnGetTokenComplete,
182                                      weak_factory_.GetWeakPtr()));
183 }
184
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;
192   }
193
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;
204   }
205   DCHECK(!token.empty());
206   // Save access token in case POST fails and we need to invalidate it.
207   access_token_ = token;
208
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);
218   }
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();
223 }
224
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();
230
231   net::URLRequestStatus status = fetcher->GetStatus();
232   diagnostic_info_.last_post_response_code_ =
233       status.is_success() ? source->GetResponseCode() : status.error();
234
235   if (status.is_success() &&
236       fetcher->GetResponseCode() == net::HTTP_UNAUTHORIZED) {
237     DVLOG(1) << "URLFetcher failure: HTTP_UNAUTHORIZED";
238     delegate_->InvalidateToken(access_token_);
239   }
240
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;
249   }
250
251   RecordOutgoingMessageStatus(OUTGOING_MESSAGE_SUCCESS);
252   // Successfully sent message. Http channel works.
253   UpdateHttpChannelState(true);
254   DVLOG(2) << "URLFetcher success";
255 }
256
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();
264
265   if (message.empty()) {
266     RecordIncomingMessageStatus(MESSAGE_EMPTY);
267     return;
268   }
269   std::string data;
270   if (!Base64DecodeURLSafe(message, &data)) {
271     RecordIncomingMessageStatus(INVALID_ENCODING);
272     return;
273   }
274   ipc::invalidation::AddressedAndroidMessage android_message;
275   if (!android_message.ParseFromString(data) ||
276       !android_message.has_message()) {
277     RecordIncomingMessageStatus(INVALID_PROTO);
278     return;
279   }
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
288 }
289
290 void GCMNetworkChannel::OnConnectionStateChanged(bool online) {
291   UpdateGcmChannelState(online);
292 }
293
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);
300 }
301
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);
310 }
311
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);
320 }
321
322 GURL GCMNetworkChannel::BuildUrl(const std::string& registration_id) {
323   DCHECK(!registration_id.empty());
324
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);
334
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);
341
342   std::string base64URLPiece;
343   Base64EncodeURLSafe(network_endpoint_id_buffer, &base64URLPiece);
344
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
353 }
354
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);
366 }
367
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);
378 }
379
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);
385 }
386
387 void GCMNetworkChannel::RequestDetailedStatus(
388     base::Callback<void(const base::DictionaryValue&)> callback) {
389   callback.Run(*diagnostic_info_.CollectDebugData());
390 }
391
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.
395 }
396
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
403 }
404
405 void GCMNetworkChannel::ResetRegisterBackoffEntryForTest(
406     const net::BackoffEntry::Policy* policy) {
407   register_backoff_entry_.reset(new net::BackoffEntry(policy));
408 }
409
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) {}
417
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();
438 }
439
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);
453   }
454   NOTREACHED();
455   return "";
456 }
457
458 }  // namespace syncer