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
26namespace syncer {
27
28namespace {
29
30const char kCacheInvalidationEndpointUrl[] =
31    "https://clients4.google.com/invalidation/android/request/";
32const char kCacheInvalidationPackageName[] = "com.google.chrome.invalidations";
33
34// Register backoff policy.
35const 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.
62enum 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.
74enum 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
85const char kIncomingMessageStatusHistogram[] =
86    "GCMInvalidations.IncomingMessageStatus";
87const char kOutgoingMessageStatusHistogram[] =
88    "GCMInvalidations.OutgoingMessageStatus";
89
90void RecordIncomingMessageStatus(IncomingMessageStatus status) {
91  UMA_HISTOGRAM_ENUMERATION(kIncomingMessageStatusHistogram,
92                            status,
93                            INCOMING_MESSAGE_STATUS_COUNT);
94}
95
96void RecordOutgoingMessageStatus(OutgoingMessageStatus status) {
97  UMA_HISTOGRAM_ENUMERATION(kOutgoingMessageStatusHistogram,
98                            status,
99                            OUTGOING_MESSAGE_STATUS_COUNT);
100}
101
102}  // namespace
103
104GCMNetworkChannel::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
120GCMNetworkChannel::~GCMNetworkChannel() {
121  net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
122}
123
124void GCMNetworkChannel::Register() {
125  delegate_->Register(base::Bind(&GCMNetworkChannel::OnRegisterComplete,
126                                 weak_factory_.GetWeakPtr()));
127}
128
129void 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
164void 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
179void GCMNetworkChannel::RequestAccessToken() {
180  DCHECK(CalledOnValidThread());
181  delegate_->RequestToken(base::Bind(&GCMNetworkChannel::OnGetTokenComplete,
182                                     weak_factory_.GetWeakPtr()));
183}
184
185void 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
225void 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
257void 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
290void GCMNetworkChannel::OnConnectionStateChanged(bool online) {
291  UpdateGcmChannelState(online);
292}
293
294void 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
302void 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
312void 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
322GURL 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
355void 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
368bool 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
380void 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
387void GCMNetworkChannel::RequestDetailedStatus(
388    base::Callback<void(const base::DictionaryValue&)> callback) {
389  callback.Run(*diagnostic_info_.CollectDebugData());
390}
391
392void 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
397int 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
405void GCMNetworkChannel::ResetRegisterBackoffEntryForTest(
406    const net::BackoffEntry::Policy* policy) {
407  register_backoff_entry_.reset(new net::BackoffEntry(policy));
408}
409
410GCMNetworkChannelDiagnostic::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
418scoped_ptr<base::DictionaryValue>
419GCMNetworkChannelDiagnostic::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
440std::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
459