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/checkin_request.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/message_loop/message_loop.h"
      9 #include "base/metrics/histogram.h"
     10 #include "google_apis/gcm/monitoring/gcm_stats_recorder.h"
     11 #include "google_apis/gcm/protocol/checkin.pb.h"
     12 #include "net/http/http_status_code.h"
     13 #include "net/url_request/url_fetcher.h"
     14 #include "net/url_request/url_request_status.h"
     15 
     16 namespace gcm {
     17 
     18 namespace {
     19 const char kRequestContentType[] = "application/x-protobuf";
     20 const int kRequestVersionValue = 3;
     21 const int kDefaultUserSerialNumber = 0;
     22 
     23 // This enum is also used in an UMA histogram (GCMCheckinRequestStatus
     24 // enum defined in tools/metrics/histograms/histogram.xml). Hence the entries
     25 // here shouldn't be deleted or re-ordered and new ones should be added to
     26 // the end, and update the GetCheckinRequestStatusString(...) below.
     27 enum CheckinRequestStatus {
     28   SUCCESS,                    // Checkin completed successfully.
     29   URL_FETCHING_FAILED,        // URL fetching failed.
     30   HTTP_BAD_REQUEST,           // The request was malformed.
     31   HTTP_UNAUTHORIZED,          // The security token didn't match the android id.
     32   HTTP_NOT_OK,                // HTTP status was not OK.
     33   RESPONSE_PARSING_FAILED,    // Check in response parsing failed.
     34   ZERO_ID_OR_TOKEN,           // Either returned android id or security token
     35                               // was zero.
     36   // NOTE: always keep this entry at the end. Add new status types only
     37   // immediately above this line. Make sure to update the corresponding
     38   // histogram enum accordingly.
     39   STATUS_COUNT
     40 };
     41 
     42 // Returns string representation of enum CheckinRequestStatus.
     43 std::string GetCheckinRequestStatusString(CheckinRequestStatus status) {
     44   switch (status) {
     45     case SUCCESS:
     46       return "SUCCESS";
     47     case URL_FETCHING_FAILED:
     48       return "URL_FETCHING_FAILED";
     49     case HTTP_BAD_REQUEST:
     50       return "HTTP_BAD_REQUEST";
     51     case HTTP_UNAUTHORIZED:
     52       return "HTTP_UNAUTHORIZED";
     53     case HTTP_NOT_OK:
     54       return "HTTP_NOT_OK";
     55     case RESPONSE_PARSING_FAILED:
     56       return "RESPONSE_PARSING_FAILED";
     57     case ZERO_ID_OR_TOKEN:
     58       return "ZERO_ID_OR_TOKEN";
     59     default:
     60       NOTREACHED();
     61       return "UNKNOWN_STATUS";
     62   }
     63 }
     64 
     65 // Records checkin status to both stats recorder and reports to UMA.
     66 void RecordCheckinStatusAndReportUMA(CheckinRequestStatus status,
     67                                      GCMStatsRecorder* recorder,
     68                                      bool will_retry) {
     69   UMA_HISTOGRAM_ENUMERATION("GCM.CheckinRequestStatus", status, STATUS_COUNT);
     70   if (status == SUCCESS)
     71     recorder->RecordCheckinSuccess();
     72   else {
     73     recorder->RecordCheckinFailure(GetCheckinRequestStatusString(status),
     74                                    will_retry);
     75   }
     76 }
     77 
     78 }  // namespace
     79 
     80 CheckinRequest::RequestInfo::RequestInfo(
     81     uint64 android_id,
     82     uint64 security_token,
     83     const std::string& settings_digest,
     84     const checkin_proto::ChromeBuildProto& chrome_build_proto)
     85     : android_id(android_id),
     86       security_token(security_token),
     87       settings_digest(settings_digest),
     88       chrome_build_proto(chrome_build_proto) {
     89 }
     90 
     91 CheckinRequest::RequestInfo::~RequestInfo() {}
     92 
     93 CheckinRequest::CheckinRequest(
     94     const GURL& checkin_url,
     95     const RequestInfo& request_info,
     96     const net::BackoffEntry::Policy& backoff_policy,
     97     const CheckinRequestCallback& callback,
     98     net::URLRequestContextGetter* request_context_getter,
     99     GCMStatsRecorder* recorder)
    100     : request_context_getter_(request_context_getter),
    101       callback_(callback),
    102       backoff_entry_(&backoff_policy),
    103       checkin_url_(checkin_url),
    104       request_info_(request_info),
    105       recorder_(recorder),
    106       weak_ptr_factory_(this) {
    107 }
    108 
    109 CheckinRequest::~CheckinRequest() {}
    110 
    111 void CheckinRequest::Start() {
    112   DCHECK(!url_fetcher_.get());
    113 
    114   checkin_proto::AndroidCheckinRequest request;
    115   request.set_id(request_info_.android_id);
    116   request.set_security_token(request_info_.security_token);
    117   request.set_user_serial_number(kDefaultUserSerialNumber);
    118   request.set_version(kRequestVersionValue);
    119   if (!request_info_.settings_digest.empty())
    120     request.set_digest(request_info_.settings_digest);
    121 
    122   checkin_proto::AndroidCheckinProto* checkin = request.mutable_checkin();
    123   checkin->mutable_chrome_build()->CopyFrom(request_info_.chrome_build_proto);
    124 #if defined(CHROME_OS)
    125   checkin->set_type(checkin_proto::DEVICE_CHROME_OS);
    126 #else
    127   checkin->set_type(checkin_proto::DEVICE_CHROME_BROWSER);
    128 #endif
    129 
    130   std::string upload_data;
    131   CHECK(request.SerializeToString(&upload_data));
    132 
    133   url_fetcher_.reset(
    134       net::URLFetcher::Create(checkin_url_, net::URLFetcher::POST, this));
    135   url_fetcher_->SetRequestContext(request_context_getter_);
    136   url_fetcher_->SetUploadData(kRequestContentType, upload_data);
    137   recorder_->RecordCheckinInitiated(request_info_.android_id);
    138   request_start_time_ = base::TimeTicks::Now();
    139   url_fetcher_->Start();
    140 }
    141 
    142 void CheckinRequest::RetryWithBackoff(bool update_backoff) {
    143   if (update_backoff) {
    144     backoff_entry_.InformOfRequest(false);
    145     url_fetcher_.reset();
    146   }
    147 
    148   if (backoff_entry_.ShouldRejectRequest()) {
    149     DVLOG(1) << "Delay GCM checkin for: "
    150              << backoff_entry_.GetTimeUntilRelease().InMilliseconds()
    151              << " milliseconds.";
    152     recorder_->RecordCheckinDelayedDueToBackoff(
    153         backoff_entry_.GetTimeUntilRelease().InMilliseconds());
    154     base::MessageLoop::current()->PostDelayedTask(
    155         FROM_HERE,
    156         base::Bind(&CheckinRequest::RetryWithBackoff,
    157                    weak_ptr_factory_.GetWeakPtr(),
    158                    false),
    159         backoff_entry_.GetTimeUntilRelease());
    160     return;
    161   }
    162 
    163   Start();
    164 }
    165 
    166 void CheckinRequest::OnURLFetchComplete(const net::URLFetcher* source) {
    167   std::string response_string;
    168   checkin_proto::AndroidCheckinResponse response_proto;
    169   if (!source->GetStatus().is_success()) {
    170     LOG(ERROR) << "Failed to get checkin response. Fetcher failed. Retrying.";
    171     RecordCheckinStatusAndReportUMA(URL_FETCHING_FAILED, recorder_, true);
    172     RetryWithBackoff(true);
    173     return;
    174   }
    175 
    176   net::HttpStatusCode response_status = static_cast<net::HttpStatusCode>(
    177       source->GetResponseCode());
    178   if (response_status == net::HTTP_BAD_REQUEST ||
    179       response_status == net::HTTP_UNAUTHORIZED) {
    180     // BAD_REQUEST indicates that the request was malformed.
    181     // UNAUTHORIZED indicates that security token didn't match the android id.
    182     LOG(ERROR) << "No point retrying the checkin with status: "
    183                << response_status << ". Checkin failed.";
    184     CheckinRequestStatus status = response_status == net::HTTP_BAD_REQUEST ?
    185         HTTP_BAD_REQUEST : HTTP_UNAUTHORIZED;
    186     RecordCheckinStatusAndReportUMA(status, recorder_, false);
    187     callback_.Run(response_proto);
    188     return;
    189   }
    190 
    191   if (response_status != net::HTTP_OK ||
    192       !source->GetResponseAsString(&response_string) ||
    193       !response_proto.ParseFromString(response_string)) {
    194     LOG(ERROR) << "Failed to get checkin response. HTTP Status: "
    195                << response_status << ". Retrying.";
    196     CheckinRequestStatus status = response_status != net::HTTP_OK ?
    197         HTTP_NOT_OK : RESPONSE_PARSING_FAILED;
    198     RecordCheckinStatusAndReportUMA(status, recorder_, true);
    199     RetryWithBackoff(true);
    200     return;
    201   }
    202 
    203   if (!response_proto.has_android_id() ||
    204       !response_proto.has_security_token() ||
    205       response_proto.android_id() == 0 ||
    206       response_proto.security_token() == 0) {
    207     LOG(ERROR) << "Android ID or security token is 0. Retrying.";
    208     RecordCheckinStatusAndReportUMA(ZERO_ID_OR_TOKEN, recorder_, true);
    209     RetryWithBackoff(true);
    210     return;
    211   }
    212 
    213   RecordCheckinStatusAndReportUMA(SUCCESS, recorder_, false);
    214   UMA_HISTOGRAM_COUNTS("GCM.CheckinRetryCount",
    215                        backoff_entry_.failure_count());
    216   UMA_HISTOGRAM_TIMES("GCM.CheckinCompleteTime",
    217                       base::TimeTicks::Now() - request_start_time_);
    218   callback_.Run(response_proto);
    219 }
    220 
    221 }  // namespace gcm
    222