OLD | NEW |
| (Empty) |
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 "base/base64.h" | |
6 #include "base/i18n/time_formatting.h" | |
7 #include "base/metrics/histogram.h" | |
8 #include "base/sha1.h" | |
9 #include "base/strings/string_number_conversions.h" | |
10 #include "base/strings/string_util.h" | |
11 #if !defined(OS_ANDROID) | |
12 // channel_common.proto defines ANDROID constant that conflicts with Android | |
13 // build. At the same time TiclInvalidationService is not used on Android so it | |
14 // is safe to exclude these protos from Android build. | |
15 #include "google/cacheinvalidation/android_channel.pb.h" | |
16 #include "google/cacheinvalidation/channel_common.pb.h" | |
17 #include "google/cacheinvalidation/types.pb.h" | |
18 #endif | |
19 #include "google_apis/gaia/google_service_auth_error.h" | |
20 #include "net/http/http_status_code.h" | |
21 #include "net/url_request/url_fetcher.h" | |
22 #include "net/url_request/url_request_status.h" | |
23 #include "sync/notifier/gcm_network_channel.h" | |
24 #include "sync/notifier/gcm_network_channel_delegate.h" | |
25 | |
26 namespace syncer { | |
27 | |
28 namespace { | |
29 | |
30 const char kCacheInvalidationEndpointUrl[] = | |
31 "https://clients4.google.com/invalidation/android/request/"; | |
32 const char kCacheInvalidationPackageName[] = "com.google.chrome.invalidations"; | |
33 | |
34 // Register backoff policy. | |
35 const net::BackoffEntry::Policy kRegisterBackoffPolicy = { | |
36 // Number of initial errors (in sequence) to ignore before applying | |
37 // exponential back-off rules. | |
38 0, | |
39 | |
40 // Initial delay for exponential back-off in ms. | |
41 2000, // 2 seconds. | |
42 | |
43 // Factor by which the waiting time will be multiplied. | |
44 2, | |
45 | |
46 // Fuzzing percentage. ex: 10% will spread requests randomly | |
47 // between 90%-100% of the calculated time. | |
48 0.2, // 20%. | |
49 | |
50 // Maximum amount of time we are willing to delay our request in ms. | |
51 1000 * 3600 * 4, // 4 hours. | |
52 | |
53 // Time to keep an entry from being discarded even when it | |
54 // has no significant state, -1 to never discard. | |
55 -1, | |
56 | |
57 // Don't use initial delay unless the last request was an error. | |
58 false, | |
59 }; | |
60 | |
61 // Incoming message status values for UMA_HISTOGRAM. | |
62 enum IncomingMessageStatus { | |
63 INCOMING_MESSAGE_SUCCESS, | |
64 MESSAGE_EMPTY, // GCM message's content is missing or empty. | |
65 INVALID_ENCODING, // Base64Decode failed. | |
66 INVALID_PROTO, // Parsing protobuf failed. | |
67 | |
68 // This enum is used in UMA_HISTOGRAM_ENUMERATION. Insert new values above | |
69 // this line. | |
70 INCOMING_MESSAGE_STATUS_COUNT | |
71 }; | |
72 | |
73 // Outgoing message status values for UMA_HISTOGRAM. | |
74 enum OutgoingMessageStatus { | |
75 OUTGOING_MESSAGE_SUCCESS, | |
76 MESSAGE_DISCARDED, // New message started before old one was sent. | |
77 ACCESS_TOKEN_FAILURE, // Requeting access token failed. | |
78 POST_FAILURE, // HTTP Post failed. | |
79 | |
80 // This enum is used in UMA_HISTOGRAM_ENUMERATION. Insert new values above | |
81 // this line. | |
82 OUTGOING_MESSAGE_STATUS_COUNT | |
83 }; | |
84 | |
85 const char kIncomingMessageStatusHistogram[] = | |
86 "GCMInvalidations.IncomingMessageStatus"; | |
87 const char kOutgoingMessageStatusHistogram[] = | |
88 "GCMInvalidations.OutgoingMessageStatus"; | |
89 | |
90 void RecordIncomingMessageStatus(IncomingMessageStatus status) { | |
91 UMA_HISTOGRAM_ENUMERATION(kIncomingMessageStatusHistogram, | |
92 status, | |
93 INCOMING_MESSAGE_STATUS_COUNT); | |
94 } | |
95 | |
96 void RecordOutgoingMessageStatus(OutgoingMessageStatus status) { | |
97 UMA_HISTOGRAM_ENUMERATION(kOutgoingMessageStatusHistogram, | |
98 status, | |
99 OUTGOING_MESSAGE_STATUS_COUNT); | |
100 } | |
101 | |
102 } // namespace | |
103 | |
104 GCMNetworkChannel::GCMNetworkChannel( | |
105 scoped_refptr<net::URLRequestContextGetter> request_context_getter, | |
106 scoped_ptr<GCMNetworkChannelDelegate> delegate) | |
107 : request_context_getter_(request_context_getter), | |
108 delegate_(delegate.Pass()), | |
109 register_backoff_entry_(new net::BackoffEntry(&kRegisterBackoffPolicy)), | |
110 diagnostic_info_(this), | |
111 weak_factory_(this) { | |
112 net::NetworkChangeNotifier::AddNetworkChangeObserver(this); | |
113 delegate_->Initialize(); | |
114 Register(); | |
115 } | |
116 | |
117 GCMNetworkChannel::~GCMNetworkChannel() { | |
118 net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this); | |
119 } | |
120 | |
121 void GCMNetworkChannel::Register() { | |
122 delegate_->Register(base::Bind(&GCMNetworkChannel::OnRegisterComplete, | |
123 weak_factory_.GetWeakPtr())); | |
124 } | |
125 | |
126 void GCMNetworkChannel::OnRegisterComplete( | |
127 const std::string& registration_id, | |
128 gcm::GCMClient::Result result) { | |
129 DCHECK(CalledOnValidThread()); | |
130 if (result == gcm::GCMClient::SUCCESS) { | |
131 DCHECK(!registration_id.empty()); | |
132 DVLOG(2) << "Got registration_id"; | |
133 register_backoff_entry_->Reset(); | |
134 registration_id_ = registration_id; | |
135 if (!cached_message_.empty()) | |
136 RequestAccessToken(); | |
137 } else { | |
138 DVLOG(2) << "Register failed: " << result; | |
139 // Retry in case of transient error. | |
140 switch (result) { | |
141 case gcm::GCMClient::NETWORK_ERROR: | |
142 case gcm::GCMClient::SERVER_ERROR: | |
143 case gcm::GCMClient::TTL_EXCEEDED: | |
144 case gcm::GCMClient::UNKNOWN_ERROR: { | |
145 register_backoff_entry_->InformOfRequest(false); | |
146 base::MessageLoop::current()->PostDelayedTask( | |
147 FROM_HERE, | |
148 base::Bind(&GCMNetworkChannel::Register, | |
149 weak_factory_.GetWeakPtr()), | |
150 register_backoff_entry_->GetTimeUntilRelease()); | |
151 break; | |
152 } | |
153 default: | |
154 break; | |
155 } | |
156 } | |
157 diagnostic_info_.registration_id_ = registration_id_; | |
158 diagnostic_info_.registration_result_ = result; | |
159 } | |
160 | |
161 void GCMNetworkChannel::SendMessage(const std::string& message) { | |
162 DCHECK(CalledOnValidThread()); | |
163 DCHECK(!message.empty()); | |
164 DVLOG(2) << "SendMessage"; | |
165 diagnostic_info_.sent_messages_count_++; | |
166 if (!cached_message_.empty()) { | |
167 RecordOutgoingMessageStatus(MESSAGE_DISCARDED); | |
168 } | |
169 cached_message_ = message; | |
170 | |
171 if (!registration_id_.empty()) { | |
172 RequestAccessToken(); | |
173 } | |
174 } | |
175 | |
176 void GCMNetworkChannel::RequestAccessToken() { | |
177 DCHECK(CalledOnValidThread()); | |
178 delegate_->RequestToken(base::Bind(&GCMNetworkChannel::OnGetTokenComplete, | |
179 weak_factory_.GetWeakPtr())); | |
180 } | |
181 | |
182 void GCMNetworkChannel::OnGetTokenComplete( | |
183 const GoogleServiceAuthError& error, | |
184 const std::string& token) { | |
185 DCHECK(CalledOnValidThread()); | |
186 if (cached_message_.empty()) { | |
187 // Nothing to do. | |
188 return; | |
189 } | |
190 | |
191 if (error.state() != GoogleServiceAuthError::NONE) { | |
192 // Requesting access token failed. Persistent errors will be reported by | |
193 // token service. Just drop this request, cacheinvalidations will retry | |
194 // sending message and at that time we'll retry requesting access token. | |
195 DVLOG(1) << "RequestAccessToken failed: " << error.ToString(); | |
196 RecordOutgoingMessageStatus(ACCESS_TOKEN_FAILURE); | |
197 // Message won't get sent because of connection failure. Let's retry once | |
198 // connection is restored. | |
199 if (error.state() == GoogleServiceAuthError::CONNECTION_FAILED) | |
200 NotifyStateChange(TRANSIENT_INVALIDATION_ERROR); | |
201 cached_message_.clear(); | |
202 return; | |
203 } | |
204 DCHECK(!token.empty()); | |
205 // Save access token in case POST fails and we need to invalidate it. | |
206 access_token_ = token; | |
207 | |
208 DVLOG(2) << "Got access token, sending message"; | |
209 fetcher_.reset(net::URLFetcher::Create( | |
210 BuildUrl(registration_id_), net::URLFetcher::POST, this)); | |
211 fetcher_->SetRequestContext(request_context_getter_); | |
212 const std::string auth_header("Authorization: Bearer " + access_token_); | |
213 fetcher_->AddExtraRequestHeader(auth_header); | |
214 if (!echo_token_.empty()) { | |
215 const std::string echo_header("echo-token: " + echo_token_); | |
216 fetcher_->AddExtraRequestHeader(echo_header); | |
217 } | |
218 fetcher_->SetUploadData("application/x-protobuffer", cached_message_); | |
219 fetcher_->Start(); | |
220 // Clear message to prevent accidentally resending it in the future. | |
221 cached_message_.clear(); | |
222 } | |
223 | |
224 void GCMNetworkChannel::OnURLFetchComplete(const net::URLFetcher* source) { | |
225 DCHECK(CalledOnValidThread()); | |
226 DCHECK_EQ(fetcher_, source); | |
227 // Free fetcher at the end of function. | |
228 scoped_ptr<net::URLFetcher> fetcher = fetcher_.Pass(); | |
229 | |
230 net::URLRequestStatus status = fetcher->GetStatus(); | |
231 diagnostic_info_.last_post_response_code_ = | |
232 status.is_success() ? source->GetResponseCode() : status.error(); | |
233 | |
234 if (status.is_success() && | |
235 fetcher->GetResponseCode() == net::HTTP_UNAUTHORIZED) { | |
236 DVLOG(1) << "URLFetcher failure: HTTP_UNAUTHORIZED"; | |
237 delegate_->InvalidateToken(access_token_); | |
238 } | |
239 | |
240 if (!status.is_success() || | |
241 (fetcher->GetResponseCode() != net::HTTP_OK && | |
242 fetcher->GetResponseCode() != net::HTTP_NO_CONTENT)) { | |
243 DVLOG(1) << "URLFetcher failure"; | |
244 RecordOutgoingMessageStatus(POST_FAILURE); | |
245 NotifyStateChange(TRANSIENT_INVALIDATION_ERROR); | |
246 return; | |
247 } | |
248 | |
249 RecordOutgoingMessageStatus(OUTGOING_MESSAGE_SUCCESS); | |
250 NotifyStateChange(INVALIDATIONS_ENABLED); | |
251 DVLOG(2) << "URLFetcher success"; | |
252 } | |
253 | |
254 void GCMNetworkChannel::OnIncomingMessage(const std::string& message, | |
255 const std::string& echo_token) { | |
256 #if !defined(OS_ANDROID) | |
257 if (!echo_token.empty()) | |
258 echo_token_ = echo_token; | |
259 diagnostic_info_.last_message_empty_echo_token_ = echo_token.empty(); | |
260 diagnostic_info_.last_message_received_time_ = base::Time::Now(); | |
261 | |
262 if (message.empty()) { | |
263 RecordIncomingMessageStatus(MESSAGE_EMPTY); | |
264 return; | |
265 } | |
266 std::string data; | |
267 if (!Base64DecodeURLSafe(message, &data)) { | |
268 RecordIncomingMessageStatus(INVALID_ENCODING); | |
269 return; | |
270 } | |
271 ipc::invalidation::AddressedAndroidMessage android_message; | |
272 if (!android_message.ParseFromString(data) || | |
273 !android_message.has_message()) { | |
274 RecordIncomingMessageStatus(INVALID_PROTO); | |
275 return; | |
276 } | |
277 DVLOG(2) << "Deliver incoming message"; | |
278 RecordIncomingMessageStatus(INCOMING_MESSAGE_SUCCESS); | |
279 DeliverIncomingMessage(android_message.message()); | |
280 #else | |
281 // This code shouldn't be invoked on Android. | |
282 NOTREACHED(); | |
283 #endif | |
284 } | |
285 | |
286 void GCMNetworkChannel::OnNetworkChanged( | |
287 net::NetworkChangeNotifier::ConnectionType connection_type) { | |
288 // Network connection is restored. Let's notify cacheinvalidations so it has | |
289 // chance to retry. | |
290 if (connection_type != net::NetworkChangeNotifier::CONNECTION_NONE) | |
291 NotifyStateChange(INVALIDATIONS_ENABLED); | |
292 } | |
293 | |
294 GURL GCMNetworkChannel::BuildUrl(const std::string& registration_id) { | |
295 DCHECK(!registration_id.empty()); | |
296 | |
297 #if !defined(OS_ANDROID) | |
298 ipc::invalidation::EndpointId endpoint_id; | |
299 endpoint_id.set_c2dm_registration_id(registration_id); | |
300 endpoint_id.set_client_key(std::string()); | |
301 endpoint_id.set_package_name(kCacheInvalidationPackageName); | |
302 endpoint_id.mutable_channel_version()->set_major_version( | |
303 ipc::invalidation::INITIAL); | |
304 std::string endpoint_id_buffer; | |
305 endpoint_id.SerializeToString(&endpoint_id_buffer); | |
306 | |
307 ipc::invalidation::NetworkEndpointId network_endpoint_id; | |
308 network_endpoint_id.set_network_address( | |
309 ipc::invalidation::NetworkEndpointId_NetworkAddress_ANDROID); | |
310 network_endpoint_id.set_client_address(endpoint_id_buffer); | |
311 std::string network_endpoint_id_buffer; | |
312 network_endpoint_id.SerializeToString(&network_endpoint_id_buffer); | |
313 | |
314 std::string base64URLPiece; | |
315 Base64EncodeURLSafe(network_endpoint_id_buffer, &base64URLPiece); | |
316 | |
317 std::string url(kCacheInvalidationEndpointUrl); | |
318 url += base64URLPiece; | |
319 return GURL(url); | |
320 #else | |
321 // This code shouldn't be invoked on Android. | |
322 NOTREACHED(); | |
323 return GURL(); | |
324 #endif | |
325 } | |
326 | |
327 void GCMNetworkChannel::Base64EncodeURLSafe(const std::string& input, | |
328 std::string* output) { | |
329 base::Base64Encode(input, output); | |
330 // Covert to url safe alphabet. | |
331 base::ReplaceChars(*output, "+", "-", output); | |
332 base::ReplaceChars(*output, "/", "_", output); | |
333 // Trim padding. | |
334 size_t padding_size = 0; | |
335 for (size_t i = output->size(); i > 0 && (*output)[i - 1] == '='; --i) | |
336 ++padding_size; | |
337 output->resize(output->size() - padding_size); | |
338 } | |
339 | |
340 bool GCMNetworkChannel::Base64DecodeURLSafe(const std::string& input, | |
341 std::string* output) { | |
342 // Add padding. | |
343 size_t padded_size = (input.size() + 3) - (input.size() + 3) % 4; | |
344 std::string padded_input(input); | |
345 padded_input.resize(padded_size, '='); | |
346 // Convert to standard base64 alphabet. | |
347 base::ReplaceChars(padded_input, "-", "+", &padded_input); | |
348 base::ReplaceChars(padded_input, "_", "/", &padded_input); | |
349 return base::Base64Decode(padded_input, output); | |
350 } | |
351 | |
352 void GCMNetworkChannel::SetMessageReceiver( | |
353 invalidation::MessageCallback* incoming_receiver) { | |
354 delegate_->SetMessageReceiver(base::Bind( | |
355 &GCMNetworkChannel::OnIncomingMessage, weak_factory_.GetWeakPtr())); | |
356 SyncNetworkChannel::SetMessageReceiver(incoming_receiver); | |
357 } | |
358 | |
359 void GCMNetworkChannel::RequestDetailedStatus( | |
360 base::Callback<void(const base::DictionaryValue&)> callback) { | |
361 callback.Run(*diagnostic_info_.CollectDebugData()); | |
362 } | |
363 | |
364 void GCMNetworkChannel::UpdateCredentials(const std::string& email, | |
365 const std::string& token) { | |
366 // Do nothing. We get access token by requesting it for every message. | |
367 } | |
368 | |
369 int GCMNetworkChannel::GetInvalidationClientType() { | |
370 #if defined(OS_IOS) | |
371 return ipc::invalidation::ClientType::CHROME_SYNC_GCM_IOS; | |
372 #else | |
373 return ipc::invalidation::ClientType::CHROME_SYNC_GCM_DESKTOP; | |
374 #endif | |
375 } | |
376 | |
377 void GCMNetworkChannel::ResetRegisterBackoffEntryForTest( | |
378 const net::BackoffEntry::Policy* policy) { | |
379 register_backoff_entry_.reset(new net::BackoffEntry(policy)); | |
380 } | |
381 | |
382 GCMNetworkChannelDiagnostic::GCMNetworkChannelDiagnostic( | |
383 GCMNetworkChannel* parent) | |
384 : parent_(parent), | |
385 last_message_empty_echo_token_(false), | |
386 last_post_response_code_(0), | |
387 registration_result_(gcm::GCMClient::UNKNOWN_ERROR), | |
388 sent_messages_count_(0) {} | |
389 | |
390 scoped_ptr<base::DictionaryValue> | |
391 GCMNetworkChannelDiagnostic::CollectDebugData() const { | |
392 scoped_ptr<base::DictionaryValue> status(new base::DictionaryValue); | |
393 status->SetString("GCMNetworkChannel.Channel", "GCM"); | |
394 std::string reg_id_hash = base::SHA1HashString(registration_id_); | |
395 status->SetString("GCMNetworkChannel.HashedRegistrationID", | |
396 base::HexEncode(reg_id_hash.c_str(), reg_id_hash.size())); | |
397 status->SetString("GCMNetworkChannel.RegistrationResult", | |
398 GCMClientResultToString(registration_result_)); | |
399 status->SetBoolean("GCMNetworkChannel.HadLastMessageEmptyEchoToken", | |
400 last_message_empty_echo_token_); | |
401 status->SetString( | |
402 "GCMNetworkChannel.LastMessageReceivedTime", | |
403 base::TimeFormatShortDateAndTime(last_message_received_time_)); | |
404 status->SetInteger("GCMNetworkChannel.LastPostResponseCode", | |
405 last_post_response_code_); | |
406 status->SetInteger("GCMNetworkChannel.SentMessages", sent_messages_count_); | |
407 status->SetInteger("GCMNetworkChannel.ReceivedMessages", | |
408 parent_->GetReceivedMessagesCount()); | |
409 return status.Pass(); | |
410 } | |
411 | |
412 std::string GCMNetworkChannelDiagnostic::GCMClientResultToString( | |
413 const gcm::GCMClient::Result result) const { | |
414 #define ENUM_CASE(x) case x: return #x; break; | |
415 switch (result) { | |
416 ENUM_CASE(gcm::GCMClient::SUCCESS); | |
417 ENUM_CASE(gcm::GCMClient::NETWORK_ERROR); | |
418 ENUM_CASE(gcm::GCMClient::SERVER_ERROR); | |
419 ENUM_CASE(gcm::GCMClient::TTL_EXCEEDED); | |
420 ENUM_CASE(gcm::GCMClient::UNKNOWN_ERROR); | |
421 ENUM_CASE(gcm::GCMClient::NOT_SIGNED_IN); | |
422 ENUM_CASE(gcm::GCMClient::INVALID_PARAMETER); | |
423 ENUM_CASE(gcm::GCMClient::ASYNC_OPERATION_PENDING); | |
424 ENUM_CASE(gcm::GCMClient::GCM_DISABLED); | |
425 } | |
426 NOTREACHED(); | |
427 return ""; | |
428 } | |
429 | |
430 } // namespace syncer | |
OLD | NEW |