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