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.
5 #include "google_apis/gcm/engine/checkin_request.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"
19 const char kRequestContentType
[] = "application/x-protobuf";
20 const int kRequestVersionValue
= 3;
21 const int kDefaultUserSerialNumber
= 0;
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
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.
42 // Returns string representation of enum CheckinRequestStatus.
43 std::string
GetCheckinRequestStatusString(CheckinRequestStatus status
) {
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";
55 case RESPONSE_PARSING_FAILED
:
56 return "RESPONSE_PARSING_FAILED";
57 case ZERO_ID_OR_TOKEN
:
58 return "ZERO_ID_OR_TOKEN";
61 return "UNKNOWN_STATUS";
65 // Records checkin status to both stats recorder and reports to UMA.
66 void RecordCheckinStatusAndReportUMA(CheckinRequestStatus status
,
67 GCMStatsRecorder
* recorder
,
69 UMA_HISTOGRAM_ENUMERATION("GCM.CheckinRequestStatus", status
, STATUS_COUNT
);
70 if (status
== SUCCESS
)
71 recorder
->RecordCheckinSuccess();
73 recorder
->RecordCheckinFailure(GetCheckinRequestStatusString(status
),
80 CheckinRequest::RequestInfo::RequestInfo(
82 uint64 security_token
,
83 const std::string
& settings_digest
,
84 const std::vector
<std::string
>& account_ids
,
85 const checkin_proto::ChromeBuildProto
& chrome_build_proto
)
86 : android_id(android_id
),
87 security_token(security_token
),
88 settings_digest(settings_digest
),
89 account_ids(account_ids
),
90 chrome_build_proto(chrome_build_proto
) {
93 CheckinRequest::RequestInfo::~RequestInfo() {}
95 CheckinRequest::CheckinRequest(
96 const GURL
& checkin_url
,
97 const RequestInfo
& request_info
,
98 const net::BackoffEntry::Policy
& backoff_policy
,
99 const CheckinRequestCallback
& callback
,
100 net::URLRequestContextGetter
* request_context_getter
,
101 GCMStatsRecorder
* recorder
)
102 : request_context_getter_(request_context_getter
),
104 backoff_entry_(&backoff_policy
),
105 checkin_url_(checkin_url
),
106 request_info_(request_info
),
108 weak_ptr_factory_(this) {
111 CheckinRequest::~CheckinRequest() {}
113 void CheckinRequest::Start() {
114 DCHECK(!url_fetcher_
.get());
116 checkin_proto::AndroidCheckinRequest request
;
117 request
.set_id(request_info_
.android_id
);
118 request
.set_security_token(request_info_
.security_token
);
119 request
.set_user_serial_number(kDefaultUserSerialNumber
);
120 request
.set_version(kRequestVersionValue
);
121 if (!request_info_
.settings_digest
.empty())
122 request
.set_digest(request_info_
.settings_digest
);
124 checkin_proto::AndroidCheckinProto
* checkin
= request
.mutable_checkin();
125 checkin
->mutable_chrome_build()->CopyFrom(request_info_
.chrome_build_proto
);
126 #if defined(CHROME_OS)
127 checkin
->set_type(checkin_proto::DEVICE_CHROME_OS
);
129 checkin
->set_type(checkin_proto::DEVICE_CHROME_BROWSER
);
132 for (std::vector
<std::string
>::const_iterator iter
=
133 request_info_
.account_ids
.begin();
134 iter
!= request_info_
.account_ids
.end();
136 request
.add_account_cookie("[" + *iter
+ "]");
139 std::string upload_data
;
140 CHECK(request
.SerializeToString(&upload_data
));
143 net::URLFetcher::Create(checkin_url_
, net::URLFetcher::POST
, this));
144 url_fetcher_
->SetRequestContext(request_context_getter_
);
145 url_fetcher_
->SetUploadData(kRequestContentType
, upload_data
);
146 recorder_
->RecordCheckinInitiated(request_info_
.android_id
);
147 request_start_time_
= base::TimeTicks::Now();
148 url_fetcher_
->Start();
151 void CheckinRequest::RetryWithBackoff(bool update_backoff
) {
152 if (update_backoff
) {
153 backoff_entry_
.InformOfRequest(false);
154 url_fetcher_
.reset();
157 if (backoff_entry_
.ShouldRejectRequest()) {
158 DVLOG(1) << "Delay GCM checkin for: "
159 << backoff_entry_
.GetTimeUntilRelease().InMilliseconds()
161 recorder_
->RecordCheckinDelayedDueToBackoff(
162 backoff_entry_
.GetTimeUntilRelease().InMilliseconds());
163 base::MessageLoop::current()->PostDelayedTask(
165 base::Bind(&CheckinRequest::RetryWithBackoff
,
166 weak_ptr_factory_
.GetWeakPtr(),
168 backoff_entry_
.GetTimeUntilRelease());
175 void CheckinRequest::OnURLFetchComplete(const net::URLFetcher
* source
) {
176 std::string response_string
;
177 checkin_proto::AndroidCheckinResponse response_proto
;
178 if (!source
->GetStatus().is_success()) {
179 LOG(ERROR
) << "Failed to get checkin response. Fetcher failed. Retrying.";
180 RecordCheckinStatusAndReportUMA(URL_FETCHING_FAILED
, recorder_
, true);
181 RetryWithBackoff(true);
185 net::HttpStatusCode response_status
= static_cast<net::HttpStatusCode
>(
186 source
->GetResponseCode());
187 if (response_status
== net::HTTP_BAD_REQUEST
||
188 response_status
== net::HTTP_UNAUTHORIZED
) {
189 // BAD_REQUEST indicates that the request was malformed.
190 // UNAUTHORIZED indicates that security token didn't match the android id.
191 LOG(ERROR
) << "No point retrying the checkin with status: "
192 << response_status
<< ". Checkin failed.";
193 CheckinRequestStatus status
= response_status
== net::HTTP_BAD_REQUEST
?
194 HTTP_BAD_REQUEST
: HTTP_UNAUTHORIZED
;
195 RecordCheckinStatusAndReportUMA(status
, recorder_
, false);
196 callback_
.Run(response_proto
);
200 if (response_status
!= net::HTTP_OK
||
201 !source
->GetResponseAsString(&response_string
) ||
202 !response_proto
.ParseFromString(response_string
)) {
203 LOG(ERROR
) << "Failed to get checkin response. HTTP Status: "
204 << response_status
<< ". Retrying.";
205 CheckinRequestStatus status
= response_status
!= net::HTTP_OK
?
206 HTTP_NOT_OK
: RESPONSE_PARSING_FAILED
;
207 RecordCheckinStatusAndReportUMA(status
, recorder_
, true);
208 RetryWithBackoff(true);
212 if (!response_proto
.has_android_id() ||
213 !response_proto
.has_security_token() ||
214 response_proto
.android_id() == 0 ||
215 response_proto
.security_token() == 0) {
216 LOG(ERROR
) << "Android ID or security token is 0. Retrying.";
217 RecordCheckinStatusAndReportUMA(ZERO_ID_OR_TOKEN
, recorder_
, true);
218 RetryWithBackoff(true);
222 RecordCheckinStatusAndReportUMA(SUCCESS
, recorder_
, false);
223 UMA_HISTOGRAM_COUNTS("GCM.CheckinRetryCount",
224 backoff_entry_
.failure_count());
225 UMA_HISTOGRAM_TIMES("GCM.CheckinCompleteTime",
226 base::TimeTicks::Now() - request_start_time_
);
227 callback_
.Run(response_proto
);