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