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