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/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"
20 const char kRequestContentType
[] = "application/x-protobuf";
21 const int kRequestVersionValue
= 3;
22 const int kDefaultUserSerialNumber
= 0;
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.
28 enum 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
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.
43 // Returns string representation of enum CheckinRequestStatus.
44 std::string
GetCheckinRequestStatusString(CheckinRequestStatus status
) {
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";
56 case RESPONSE_PARSING_FAILED
:
57 return "RESPONSE_PARSING_FAILED";
58 case ZERO_ID_OR_TOKEN
:
59 return "ZERO_ID_OR_TOKEN";
62 return "UNKNOWN_STATUS";
66 // Records checkin status to both stats recorder and reports to UMA.
67 void RecordCheckinStatusAndReportUMA(CheckinRequestStatus status
,
68 GCMStatsRecorder
* recorder
,
70 UMA_HISTOGRAM_ENUMERATION("GCM.CheckinRequestStatus", status
, STATUS_COUNT
);
71 if (status
== SUCCESS
)
72 recorder
->RecordCheckinSuccess();
74 recorder
->RecordCheckinFailure(GetCheckinRequestStatusString(status
),
81 CheckinRequest::RequestInfo::RequestInfo(
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
) {
94 CheckinRequest::RequestInfo::~RequestInfo() {}
96 CheckinRequest::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
),
105 backoff_entry_(&backoff_policy
),
106 checkin_url_(checkin_url
),
107 request_info_(request_info
),
109 weak_ptr_factory_(this) {
112 CheckinRequest::~CheckinRequest() {}
114 void CheckinRequest::Start() {
115 DCHECK(!url_fetcher_
.get());
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
);
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
);
130 checkin
->set_type(checkin_proto::DEVICE_CHROME_BROWSER
);
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();
139 request
.add_account_cookie(iter
->first
);
140 request
.add_account_cookie(iter
->second
);
143 std::string upload_data
;
144 CHECK(request
.SerializeToString(&upload_data
));
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();
157 void CheckinRequest::RetryWithBackoff(bool update_backoff
) {
158 if (update_backoff
) {
159 backoff_entry_
.InformOfRequest(false);
160 url_fetcher_
.reset();
163 if (backoff_entry_
.ShouldRejectRequest()) {
164 DVLOG(1) << "Delay GCM checkin for: "
165 << backoff_entry_
.GetTimeUntilRelease().InMilliseconds()
167 recorder_
->RecordCheckinDelayedDueToBackoff(
168 backoff_entry_
.GetTimeUntilRelease().InMilliseconds());
169 base::MessageLoop::current()->PostDelayedTask(
171 base::Bind(&CheckinRequest::RetryWithBackoff
,
172 weak_ptr_factory_
.GetWeakPtr(),
174 backoff_entry_
.GetTimeUntilRelease());
181 void 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);
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
);
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);
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);
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
);