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