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 |