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/location.h"
9 #include "base/metrics/histogram.h"
10 #include "base/thread_task_runner_handle.h"
11 #include "google_apis/gcm/monitoring/gcm_stats_recorder.h"
12 #include "google_apis/gcm/protocol/checkin.pb.h"
13 #include "net/base/load_flags.h"
14 #include "net/http/http_status_code.h"
15 #include "net/url_request/url_fetcher.h"
16 #include "net/url_request/url_request_status.h"
21 const char kRequestContentType
[] = "application/x-protobuf";
22 const int kRequestVersionValue
= 3;
23 const int kDefaultUserSerialNumber
= 0;
25 // This enum is also used in an UMA histogram (GCMCheckinRequestStatus
26 // enum defined in tools/metrics/histograms/histogram.xml). Hence the entries
27 // here shouldn't be deleted or re-ordered and new ones should be added to
28 // the end, and update the GetCheckinRequestStatusString(...) below.
29 enum CheckinRequestStatus
{
30 SUCCESS
, // Checkin completed successfully.
31 URL_FETCHING_FAILED
, // URL fetching failed.
32 HTTP_BAD_REQUEST
, // The request was malformed.
33 HTTP_UNAUTHORIZED
, // The security token didn't match the android id.
34 HTTP_NOT_OK
, // HTTP status was not OK.
35 RESPONSE_PARSING_FAILED
, // Check in response parsing failed.
36 ZERO_ID_OR_TOKEN
, // Either returned android id or security token
38 // NOTE: always keep this entry at the end. Add new status types only
39 // immediately above this line. Make sure to update the corresponding
40 // histogram enum accordingly.
44 // Returns string representation of enum CheckinRequestStatus.
45 std::string
GetCheckinRequestStatusString(CheckinRequestStatus status
) {
49 case URL_FETCHING_FAILED
:
50 return "URL_FETCHING_FAILED";
51 case HTTP_BAD_REQUEST
:
52 return "HTTP_BAD_REQUEST";
53 case HTTP_UNAUTHORIZED
:
54 return "HTTP_UNAUTHORIZED";
57 case RESPONSE_PARSING_FAILED
:
58 return "RESPONSE_PARSING_FAILED";
59 case ZERO_ID_OR_TOKEN
:
60 return "ZERO_ID_OR_TOKEN";
63 return "UNKNOWN_STATUS";
67 // Records checkin status to both stats recorder and reports to UMA.
68 void RecordCheckinStatusAndReportUMA(CheckinRequestStatus status
,
69 GCMStatsRecorder
* recorder
,
71 UMA_HISTOGRAM_ENUMERATION("GCM.CheckinRequestStatus", status
, STATUS_COUNT
);
72 if (status
== SUCCESS
)
73 recorder
->RecordCheckinSuccess();
75 recorder
->RecordCheckinFailure(GetCheckinRequestStatusString(status
),
82 CheckinRequest::RequestInfo::RequestInfo(
84 uint64 security_token
,
85 const std::map
<std::string
, std::string
>& account_tokens
,
86 const std::string
& settings_digest
,
87 const checkin_proto::ChromeBuildProto
& chrome_build_proto
)
88 : android_id(android_id
),
89 security_token(security_token
),
90 account_tokens(account_tokens
),
91 settings_digest(settings_digest
),
92 chrome_build_proto(chrome_build_proto
) {
95 CheckinRequest::RequestInfo::~RequestInfo() {}
97 CheckinRequest::CheckinRequest(
98 const GURL
& checkin_url
,
99 const RequestInfo
& request_info
,
100 const net::BackoffEntry::Policy
& backoff_policy
,
101 const CheckinRequestCallback
& callback
,
102 net::URLRequestContextGetter
* request_context_getter
,
103 GCMStatsRecorder
* recorder
)
104 : request_context_getter_(request_context_getter
),
106 backoff_entry_(&backoff_policy
),
107 checkin_url_(checkin_url
),
108 request_info_(request_info
),
110 weak_ptr_factory_(this) {
113 CheckinRequest::~CheckinRequest() {}
115 void CheckinRequest::Start() {
116 DCHECK(!url_fetcher_
.get());
118 checkin_proto::AndroidCheckinRequest request
;
119 request
.set_id(request_info_
.android_id
);
120 request
.set_security_token(request_info_
.security_token
);
121 request
.set_user_serial_number(kDefaultUserSerialNumber
);
122 request
.set_version(kRequestVersionValue
);
123 if (!request_info_
.settings_digest
.empty())
124 request
.set_digest(request_info_
.settings_digest
);
126 checkin_proto::AndroidCheckinProto
* checkin
= request
.mutable_checkin();
127 checkin
->mutable_chrome_build()->CopyFrom(request_info_
.chrome_build_proto
);
128 #if defined(CHROME_OS)
129 checkin
->set_type(checkin_proto::DEVICE_CHROME_OS
);
131 checkin
->set_type(checkin_proto::DEVICE_CHROME_BROWSER
);
134 // Pack a map of email -> token mappings into a repeated field, where odd
135 // entries are email addresses, while even ones are respective OAuth2 tokens.
136 for (std::map
<std::string
, std::string
>::const_iterator iter
=
137 request_info_
.account_tokens
.begin();
138 iter
!= request_info_
.account_tokens
.end();
140 request
.add_account_cookie(iter
->first
);
141 request
.add_account_cookie(iter
->second
);
144 std::string upload_data
;
145 CHECK(request
.SerializeToString(&upload_data
));
148 net::URLFetcher::Create(checkin_url_
, net::URLFetcher::POST
, this);
149 url_fetcher_
->SetRequestContext(request_context_getter_
);
150 url_fetcher_
->SetUploadData(kRequestContentType
, upload_data
);
151 url_fetcher_
->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES
|
152 net::LOAD_DO_NOT_SAVE_COOKIES
);
153 recorder_
->RecordCheckinInitiated(request_info_
.android_id
);
154 request_start_time_
= base::TimeTicks::Now();
155 url_fetcher_
->Start();
158 void CheckinRequest::RetryWithBackoff() {
159 backoff_entry_
.InformOfRequest(false);
160 url_fetcher_
.reset();
162 DVLOG(1) << "Delay GCM checkin for: "
163 << backoff_entry_
.GetTimeUntilRelease().InMilliseconds()
165 recorder_
->RecordCheckinDelayedDueToBackoff(
166 backoff_entry_
.GetTimeUntilRelease().InMilliseconds());
167 DCHECK(!weak_ptr_factory_
.HasWeakPtrs());
168 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
170 base::Bind(&CheckinRequest::Start
, weak_ptr_factory_
.GetWeakPtr()),
171 backoff_entry_
.GetTimeUntilRelease());
174 void CheckinRequest::OnURLFetchComplete(const net::URLFetcher
* source
) {
175 std::string response_string
;
176 checkin_proto::AndroidCheckinResponse response_proto
;
177 if (!source
->GetStatus().is_success()) {
178 LOG(ERROR
) << "Failed to get checkin response. Fetcher failed. Retrying.";
179 RecordCheckinStatusAndReportUMA(URL_FETCHING_FAILED
, recorder_
, true);
184 net::HttpStatusCode response_status
= static_cast<net::HttpStatusCode
>(
185 source
->GetResponseCode());
186 if (response_status
== net::HTTP_BAD_REQUEST
||
187 response_status
== net::HTTP_UNAUTHORIZED
) {
188 // BAD_REQUEST indicates that the request was malformed.
189 // UNAUTHORIZED indicates that security token didn't match the android id.
190 LOG(ERROR
) << "No point retrying the checkin with status: "
191 << response_status
<< ". Checkin failed.";
192 CheckinRequestStatus status
= response_status
== net::HTTP_BAD_REQUEST
?
193 HTTP_BAD_REQUEST
: HTTP_UNAUTHORIZED
;
194 RecordCheckinStatusAndReportUMA(status
, recorder_
, false);
195 callback_
.Run(response_proto
);
199 if (response_status
!= net::HTTP_OK
||
200 !source
->GetResponseAsString(&response_string
) ||
201 !response_proto
.ParseFromString(response_string
)) {
202 LOG(ERROR
) << "Failed to get checkin response. HTTP Status: "
203 << response_status
<< ". Retrying.";
204 CheckinRequestStatus status
= response_status
!= net::HTTP_OK
?
205 HTTP_NOT_OK
: RESPONSE_PARSING_FAILED
;
206 RecordCheckinStatusAndReportUMA(status
, recorder_
, true);
211 if (!response_proto
.has_android_id() ||
212 !response_proto
.has_security_token() ||
213 response_proto
.android_id() == 0 ||
214 response_proto
.security_token() == 0) {
215 LOG(ERROR
) << "Android ID or security token is 0. Retrying.";
216 RecordCheckinStatusAndReportUMA(ZERO_ID_OR_TOKEN
, recorder_
, true);
221 RecordCheckinStatusAndReportUMA(SUCCESS
, recorder_
, false);
222 UMA_HISTOGRAM_COUNTS("GCM.CheckinRetryCount",
223 backoff_entry_
.failure_count());
224 UMA_HISTOGRAM_TIMES("GCM.CheckinCompleteTime",
225 base::TimeTicks::Now() - request_start_time_
);
226 callback_
.Run(response_proto
);