registration_request.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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 "net/base/escape.h"
13#include "net/http/http_request_headers.h"
14#include "net/http/http_status_code.h"
15#include "net/url_request/url_fetcher.h"
16#include "net/url_request/url_request_context_getter.h"
17#include "net/url_request/url_request_status.h"
18#include "url/gurl.h"
19
20namespace gcm {
21
22namespace {
23
24const char kRegistrationURL[] =
25    "https://android.clients.google.com/c2dm/register3";
26const char kRegistrationRequestContentType[] =
27    "application/x-www-form-urlencoded";
28
29// Request constants.
30const char kAppIdKey[] = "app";
31const char kDeviceIdKey[] = "device";
32const char kLoginHeader[] = "AidLogin";
33const char kSenderKey[] = "sender";
34
35// Request validation constants.
36const size_t kMaxSenders = 100;
37
38// Response constants.
39const char kErrorPrefix[] = "Error=";
40const char kTokenPrefix[] = "token=";
41const char kDeviceRegistrationError[] = "PHONE_REGISTRATION_ERROR";
42const char kAuthenticationFailed[] = "AUTHENTICATION_FAILED";
43const char kInvalidSender[] = "INVALID_SENDER";
44const char kInvalidParameters[] = "INVALID_PARAMETERS";
45
46void 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.
55RegistrationRequest::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.
71bool 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
80void RecordRegistrationStatusToUMA(RegistrationRequest::Status status) {
81  UMA_HISTOGRAM_ENUMERATION("GCM.RegistrationRequestStatus", status,
82                            RegistrationRequest::STATUS_COUNT);
83}
84
85}  // namespace
86
87RegistrationRequest::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
98RegistrationRequest::RequestInfo::~RequestInfo() {}
99
100RegistrationRequest::RegistrationRequest(
101    const RequestInfo& request_info,
102    const net::BackoffEntry::Policy& backoff_policy,
103    const RegistrationCallback& callback,
104    int max_retry_count,
105    scoped_refptr<net::URLRequestContextGetter> request_context_getter)
106    : callback_(callback),
107      request_info_(request_info),
108      backoff_entry_(&backoff_policy),
109      request_context_getter_(request_context_getter),
110      retries_left_(max_retry_count),
111      weak_ptr_factory_(this) {
112  DCHECK_GE(max_retry_count, 0);
113}
114
115RegistrationRequest::~RegistrationRequest() {}
116
117void RegistrationRequest::Start() {
118  DCHECK(!callback_.is_null());
119  DCHECK(request_info_.android_id != 0UL);
120  DCHECK(request_info_.security_token != 0UL);
121  DCHECK(0 < request_info_.sender_ids.size() &&
122         request_info_.sender_ids.size() <= kMaxSenders);
123
124  DCHECK(!url_fetcher_.get());
125  url_fetcher_.reset(net::URLFetcher::Create(
126      GURL(kRegistrationURL), net::URLFetcher::POST, this));
127  url_fetcher_->SetRequestContext(request_context_getter_);
128
129  std::string android_id = base::Uint64ToString(request_info_.android_id);
130  std::string auth_header =
131      std::string(net::HttpRequestHeaders::kAuthorization) + ": " +
132      kLoginHeader + " " + android_id + ":" +
133      base::Uint64ToString(request_info_.security_token);
134  url_fetcher_->SetExtraRequestHeaders(auth_header);
135
136  std::string body;
137  BuildFormEncoding(kAppIdKey, request_info_.app_id, &body);
138  BuildFormEncoding(kDeviceIdKey, android_id, &body);
139
140  std::string senders;
141  for (std::vector<std::string>::const_iterator iter =
142           request_info_.sender_ids.begin();
143       iter != request_info_.sender_ids.end();
144       ++iter) {
145    DCHECK(!iter->empty());
146    if (!senders.empty())
147      senders.append(",");
148    senders.append(*iter);
149  }
150  BuildFormEncoding(kSenderKey, senders, &body);
151
152  DVLOG(1) << "Performing registration for: " << request_info_.app_id;
153  DVLOG(1) << "Registration request: " << body;
154  url_fetcher_->SetUploadData(kRegistrationRequestContentType, body);
155  url_fetcher_->Start();
156}
157
158void RegistrationRequest::RetryWithBackoff(bool update_backoff) {
159  if (update_backoff) {
160    DCHECK_GT(retries_left_, 0);
161    --retries_left_;
162    url_fetcher_.reset();
163    backoff_entry_.InformOfRequest(false);
164  }
165
166  if (backoff_entry_.ShouldRejectRequest()) {
167    DVLOG(1) << "Delaying GCM registration of app: "
168             << request_info_.app_id << ", for "
169             << backoff_entry_.GetTimeUntilRelease().InMilliseconds()
170             << " milliseconds.";
171    base::MessageLoop::current()->PostDelayedTask(
172        FROM_HERE,
173        base::Bind(&RegistrationRequest::RetryWithBackoff,
174                   weak_ptr_factory_.GetWeakPtr(),
175                   false),
176        backoff_entry_.GetTimeUntilRelease());
177    return;
178  }
179
180  Start();
181}
182
183RegistrationRequest::Status RegistrationRequest::ParseResponse(
184    const net::URLFetcher* source, std::string* token) {
185  if (!source->GetStatus().is_success()) {
186    LOG(ERROR) << "URL fetching failed.";
187    return URL_FETCHING_FAILED;
188  }
189
190  std::string response;
191  if (!source->GetResponseAsString(&response)) {
192    LOG(ERROR) << "Failed to parse registration response as a string.";
193    return RESPONSE_PARSING_FAILED;
194  }
195
196  if (source->GetResponseCode() == net::HTTP_OK) {
197    size_t token_pos = response.find(kTokenPrefix);
198    if (token_pos != std::string::npos) {
199      *token = response.substr(token_pos + arraysize(kTokenPrefix) - 1);
200      return SUCCESS;
201    }
202  }
203
204  // If we are able to parse a meaningful known error, let's do so. Some errors
205  // will have HTTP_BAD_REQUEST, some will have HTTP_OK response code.
206  size_t error_pos = response.find(kErrorPrefix);
207  if (error_pos != std::string::npos) {
208    std::string error = response.substr(
209        error_pos + arraysize(kErrorPrefix) - 1);
210    return GetStatusFromError(error);
211  }
212
213  // If we cannot tell what the error is, but at least we know response code was
214  // not OK.
215  if (source->GetResponseCode() != net::HTTP_OK) {
216    DLOG(ERROR) << "URL fetching HTTP response code is not OK. It is "
217                << source->GetResponseCode();
218    return HTTP_NOT_OK;
219  }
220
221  return UNKNOWN_ERROR;
222}
223
224void RegistrationRequest::OnURLFetchComplete(const net::URLFetcher* source) {
225  std::string token;
226  Status status = ParseResponse(source, &token);
227  RecordRegistrationStatusToUMA(status);
228
229  if (ShouldRetryWithStatus(status)) {
230    if (retries_left_ > 0) {
231      RetryWithBackoff(true);
232      return;
233    }
234
235    status = REACHED_MAX_RETRIES;
236    RecordRegistrationStatusToUMA(status);
237  }
238
239  callback_.Run(status, token);
240}
241
242}  // namespace gcm
243