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