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