Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(181)

Side by Side Diff: sync/notifier/gcm_network_channel.cc

Issue 294123004: Move some sync/notifier to components/invalidation (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Rebase Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « sync/notifier/gcm_network_channel.h ('k') | sync/notifier/gcm_network_channel_delegate.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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
OLDNEW
« no previous file with comments | « sync/notifier/gcm_network_channel.h ('k') | sync/notifier/gcm_network_channel_delegate.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698