Home | History | Annotate | Download | only in engine
      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 "google_apis/gcm/engine/registration_request.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/message_loop/message_loop.h"
      9 #include "base/metrics/histogram.h"
     10 #include "base/strings/string_number_conversions.h"
     11 #include "base/values.h"
     12 #include "google_apis/gcm/monitoring/gcm_stats_recorder.h"
     13 #include "net/base/escape.h"
     14 #include "net/http/http_request_headers.h"
     15 #include "net/http/http_status_code.h"
     16 #include "net/url_request/url_fetcher.h"
     17 #include "net/url_request/url_request_context_getter.h"
     18 #include "net/url_request/url_request_status.h"
     19 #include "url/gurl.h"
     20 
     21 namespace gcm {
     22 
     23 namespace {
     24 
     25 const char kRegistrationRequestContentType[] =
     26     "application/x-www-form-urlencoded";
     27 
     28 // Request constants.
     29 const char kAppIdKey[] = "app";
     30 const char kDeviceIdKey[] = "device";
     31 const char kLoginHeader[] = "AidLogin";
     32 const char kSenderKey[] = "sender";
     33 
     34 // Request validation constants.
     35 const size_t kMaxSenders = 100;
     36 
     37 // Response constants.
     38 const char kErrorPrefix[] = "Error=";
     39 const char kTokenPrefix[] = "token=";
     40 const char kDeviceRegistrationError[] = "PHONE_REGISTRATION_ERROR";
     41 const char kAuthenticationFailed[] = "AUTHENTICATION_FAILED";
     42 const char kInvalidSender[] = "INVALID_SENDER";
     43 const char kInvalidParameters[] = "INVALID_PARAMETERS";
     44 
     45 void BuildFormEncoding(const std::string& key,
     46                        const std::string& value,
     47                        std::string* out) {
     48   if (!out->empty())
     49     out->append("&");
     50   out->append(key + "=" + net::EscapeUrlEncodedData(value, true));
     51 }
     52 
     53 // Gets correct status from the error message.
     54 RegistrationRequest::Status GetStatusFromError(const std::string& error) {
     55   // TODO(fgorski): Improve error parsing in case there is nore then just an
     56   // Error=ERROR_STRING in response.
     57   if (error.find(kDeviceRegistrationError) != std::string::npos)
     58     return RegistrationRequest::DEVICE_REGISTRATION_ERROR;
     59   if (error.find(kAuthenticationFailed) != std::string::npos)
     60     return RegistrationRequest::AUTHENTICATION_FAILED;
     61   if (error.find(kInvalidSender) != std::string::npos)
     62     return RegistrationRequest::INVALID_SENDER;
     63   if (error.find(kInvalidParameters) != std::string::npos)
     64     return RegistrationRequest::INVALID_PARAMETERS;
     65   return RegistrationRequest::UNKNOWN_ERROR;
     66 }
     67 
     68 // Indicates whether a retry attempt should be made based on the status of the
     69 // last request.
     70 bool ShouldRetryWithStatus(RegistrationRequest::Status status) {
     71   return status == RegistrationRequest::UNKNOWN_ERROR ||
     72          status == RegistrationRequest::AUTHENTICATION_FAILED ||
     73          status == RegistrationRequest::DEVICE_REGISTRATION_ERROR ||
     74          status == RegistrationRequest::HTTP_NOT_OK ||
     75          status == RegistrationRequest::URL_FETCHING_FAILED ||
     76          status == RegistrationRequest::RESPONSE_PARSING_FAILED;
     77 }
     78 
     79 void RecordRegistrationStatusToUMA(RegistrationRequest::Status status) {
     80   UMA_HISTOGRAM_ENUMERATION("GCM.RegistrationRequestStatus", status,
     81                             RegistrationRequest::STATUS_COUNT);
     82 }
     83 
     84 }  // namespace
     85 
     86 RegistrationRequest::RequestInfo::RequestInfo(
     87     uint64 android_id,
     88     uint64 security_token,
     89     const std::string& app_id,
     90     const std::vector<std::string>& sender_ids)
     91     : android_id(android_id),
     92       security_token(security_token),
     93       app_id(app_id),
     94       sender_ids(sender_ids) {
     95 }
     96 
     97 RegistrationRequest::RequestInfo::~RequestInfo() {}
     98 
     99 RegistrationRequest::RegistrationRequest(
    100     const GURL& registration_url,
    101     const RequestInfo& request_info,
    102     const net::BackoffEntry::Policy& backoff_policy,
    103     const RegistrationCallback& callback,
    104     int max_retry_count,
    105     scoped_refptr<net::URLRequestContextGetter> request_context_getter,
    106     GCMStatsRecorder* recorder)
    107     : callback_(callback),
    108       request_info_(request_info),
    109       registration_url_(registration_url),
    110       backoff_entry_(&backoff_policy),
    111       request_context_getter_(request_context_getter),
    112       retries_left_(max_retry_count),
    113       recorder_(recorder),
    114       weak_ptr_factory_(this) {
    115   DCHECK_GE(max_retry_count, 0);
    116 }
    117 
    118 RegistrationRequest::~RegistrationRequest() {}
    119 
    120 void RegistrationRequest::Start() {
    121   DCHECK(!callback_.is_null());
    122   DCHECK(request_info_.android_id != 0UL);
    123   DCHECK(request_info_.security_token != 0UL);
    124   DCHECK(0 < request_info_.sender_ids.size() &&
    125          request_info_.sender_ids.size() <= kMaxSenders);
    126 
    127   DCHECK(!url_fetcher_.get());
    128   url_fetcher_.reset(net::URLFetcher::Create(
    129       registration_url_, net::URLFetcher::POST, this));
    130   url_fetcher_->SetRequestContext(request_context_getter_);
    131 
    132   std::string android_id = base::Uint64ToString(request_info_.android_id);
    133   std::string auth_header =
    134       std::string(net::HttpRequestHeaders::kAuthorization) + ": " +
    135       kLoginHeader + " " + android_id + ":" +
    136       base::Uint64ToString(request_info_.security_token);
    137   url_fetcher_->SetExtraRequestHeaders(auth_header);
    138 
    139   std::string body;
    140   BuildFormEncoding(kAppIdKey, request_info_.app_id, &body);
    141   BuildFormEncoding(kDeviceIdKey, android_id, &body);
    142 
    143   std::string senders;
    144   for (std::vector<std::string>::const_iterator iter =
    145            request_info_.sender_ids.begin();
    146        iter != request_info_.sender_ids.end();
    147        ++iter) {
    148     DCHECK(!iter->empty());
    149     if (!senders.empty())
    150       senders.append(",");
    151     senders.append(*iter);
    152   }
    153   BuildFormEncoding(kSenderKey, senders, &body);
    154   UMA_HISTOGRAM_COUNTS("GCM.RegistrationSenderIdCount",
    155                        request_info_.sender_ids.size());
    156 
    157   DVLOG(1) << "Performing registration for: " << request_info_.app_id;
    158   DVLOG(1) << "Registration request: " << body;
    159   url_fetcher_->SetUploadData(kRegistrationRequestContentType, body);
    160   recorder_->RecordRegistrationSent(request_info_.app_id, senders);
    161   request_start_time_ = base::TimeTicks::Now();
    162   url_fetcher_->Start();
    163 }
    164 
    165 void RegistrationRequest::RetryWithBackoff(bool update_backoff) {
    166   if (update_backoff) {
    167     DCHECK_GT(retries_left_, 0);
    168     --retries_left_;
    169     url_fetcher_.reset();
    170     backoff_entry_.InformOfRequest(false);
    171   }
    172 
    173   if (backoff_entry_.ShouldRejectRequest()) {
    174     DVLOG(1) << "Delaying GCM registration of app: "
    175              << request_info_.app_id << ", for "
    176              << backoff_entry_.GetTimeUntilRelease().InMilliseconds()
    177              << " milliseconds.";
    178     base::MessageLoop::current()->PostDelayedTask(
    179         FROM_HERE,
    180         base::Bind(&RegistrationRequest::RetryWithBackoff,
    181                    weak_ptr_factory_.GetWeakPtr(),
    182                    false),
    183         backoff_entry_.GetTimeUntilRelease());
    184     return;
    185   }
    186 
    187   Start();
    188 }
    189 
    190 RegistrationRequest::Status RegistrationRequest::ParseResponse(
    191     const net::URLFetcher* source, std::string* token) {
    192   if (!source->GetStatus().is_success()) {
    193     LOG(ERROR) << "URL fetching failed.";
    194     return URL_FETCHING_FAILED;
    195   }
    196 
    197   std::string response;
    198   if (!source->GetResponseAsString(&response)) {
    199     LOG(ERROR) << "Failed to parse registration response as a string.";
    200     return RESPONSE_PARSING_FAILED;
    201   }
    202 
    203   if (source->GetResponseCode() == net::HTTP_OK) {
    204     size_t token_pos = response.find(kTokenPrefix);
    205     if (token_pos != std::string::npos) {
    206       *token = response.substr(token_pos + arraysize(kTokenPrefix) - 1);
    207       return SUCCESS;
    208     }
    209   }
    210 
    211   // If we are able to parse a meaningful known error, let's do so. Some errors
    212   // will have HTTP_BAD_REQUEST, some will have HTTP_OK response code.
    213   size_t error_pos = response.find(kErrorPrefix);
    214   if (error_pos != std::string::npos) {
    215     std::string error = response.substr(
    216         error_pos + arraysize(kErrorPrefix) - 1);
    217     return GetStatusFromError(error);
    218   }
    219 
    220   // If we cannot tell what the error is, but at least we know response code was
    221   // not OK.
    222   if (source->GetResponseCode() != net::HTTP_OK) {
    223     DLOG(ERROR) << "URL fetching HTTP response code is not OK. It is "
    224                 << source->GetResponseCode();
    225     return HTTP_NOT_OK;
    226   }
    227 
    228   return UNKNOWN_ERROR;
    229 }
    230 
    231 void RegistrationRequest::OnURLFetchComplete(const net::URLFetcher* source) {
    232   std::string token;
    233   Status status = ParseResponse(source, &token);
    234   RecordRegistrationStatusToUMA(status);
    235   recorder_->RecordRegistrationResponse(
    236       request_info_.app_id,
    237       request_info_.sender_ids,
    238       status);
    239 
    240   if (ShouldRetryWithStatus(status)) {
    241     if (retries_left_ > 0) {
    242       recorder_->RecordRegistrationRetryRequested(
    243           request_info_.app_id,
    244           request_info_.sender_ids,
    245           retries_left_);
    246       RetryWithBackoff(true);
    247       return;
    248     }
    249 
    250     status = REACHED_MAX_RETRIES;
    251     recorder_->RecordRegistrationResponse(
    252         request_info_.app_id,
    253         request_info_.sender_ids,
    254         status);
    255     RecordRegistrationStatusToUMA(status);
    256   }
    257 
    258   if (status == SUCCESS) {
    259     UMA_HISTOGRAM_COUNTS("GCM.RegistrationRetryCount",
    260                          backoff_entry_.failure_count());
    261     UMA_HISTOGRAM_TIMES("GCM.RegistrationCompleteTime",
    262                         base::TimeTicks::Now() - request_start_time_);
    263   }
    264   callback_.Run(status, token);
    265 }
    266 
    267 }  // namespace gcm
    268