Home | History | Annotate | Download | only in invalidation
      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_);
    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
    459