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