gcm_client_impl.cc revision f8ee788a64d60abd8f2d742a5fdedde054ecd910
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 "components/gcm_driver/gcm_client_impl.h" 6 7#include "base/bind.h" 8#include "base/files/file_path.h" 9#include "base/logging.h" 10#include "base/memory/scoped_ptr.h" 11#include "base/message_loop/message_loop.h" 12#include "base/metrics/histogram.h" 13#include "base/sequenced_task_runner.h" 14#include "base/strings/string_number_conversions.h" 15#include "base/strings/stringprintf.h" 16#include "base/time/default_clock.h" 17#include "google_apis/gcm/base/encryptor.h" 18#include "google_apis/gcm/base/mcs_message.h" 19#include "google_apis/gcm/base/mcs_util.h" 20#include "google_apis/gcm/engine/checkin_request.h" 21#include "google_apis/gcm/engine/connection_factory_impl.h" 22#include "google_apis/gcm/engine/gcm_store_impl.h" 23#include "google_apis/gcm/monitoring/gcm_stats_recorder.h" 24#include "google_apis/gcm/protocol/checkin.pb.h" 25#include "google_apis/gcm/protocol/mcs.pb.h" 26#include "net/http/http_network_session.h" 27#include "net/url_request/url_request_context.h" 28#include "url/gurl.h" 29 30namespace gcm { 31 32namespace { 33 34// Backoff policy. Shared across reconnection logic and checkin/(un)registration 35// retries. 36// Note: In order to ensure a minimum of 20 seconds between server errors (for 37// server reasons), we have a 30s +- 10s (33%) jitter initial backoff. 38// TODO(zea): consider sharing/synchronizing the scheduling of backoff retries 39// themselves. 40const net::BackoffEntry::Policy kDefaultBackoffPolicy = { 41 // Number of initial errors (in sequence) to ignore before applying 42 // exponential back-off rules. 43 0, 44 45 // Initial delay for exponential back-off in ms. 46 30 * 1000, // 30 seconds. 47 48 // Factor by which the waiting time will be multiplied. 49 2, 50 51 // Fuzzing percentage. ex: 10% will spread requests randomly 52 // between 90%-100% of the calculated time. 53 0.33, // 33%. 54 55 // Maximum amount of time we are willing to delay our request in ms. 56 10 * 60 * 1000, // 10 minutes. 57 58 // Time to keep an entry from being discarded even when it 59 // has no significant state, -1 to never discard. 60 -1, 61 62 // Don't use initial delay unless the last request was an error. 63 false, 64}; 65 66// Indicates a message type of the received message. 67enum MessageType { 68 UNKNOWN, // Undetermined type. 69 DATA_MESSAGE, // Regular data message. 70 DELETED_MESSAGES, // Messages were deleted on the server. 71 SEND_ERROR, // Error sending a message. 72}; 73 74enum OutgoingMessageTTLCategory { 75 TTL_ZERO, 76 TTL_LESS_THAN_OR_EQUAL_TO_ONE_MINUTE, 77 TTL_LESS_THAN_OR_EQUAL_TO_ONE_HOUR, 78 TTL_LESS_THAN_OR_EQUAL_TO_ONE_DAY, 79 TTL_LESS_THAN_OR_EQUAL_TO_ONE_WEEK, 80 TTL_MORE_THAN_ONE_WEEK, 81 TTL_MAXIMUM, 82 // NOTE: always keep this entry at the end. Add new TTL category only 83 // immediately above this line. Make sure to update the corresponding 84 // histogram enum accordingly. 85 TTL_CATEGORY_COUNT 86}; 87 88const int kMaxRegistrationRetries = 5; 89const char kMessageTypeDataMessage[] = "gcm"; 90const char kMessageTypeDeletedMessagesKey[] = "deleted_messages"; 91const char kMessageTypeKey[] = "message_type"; 92const char kMessageTypeSendErrorKey[] = "send_error"; 93const char kSendErrorMessageIdKey[] = "google.message_id"; 94const char kSendMessageFromValue[] = "gcm@chrome.com"; 95const int64 kDefaultUserSerialNumber = 0LL; 96 97GCMClient::Result ToGCMClientResult(MCSClient::MessageSendStatus status) { 98 switch (status) { 99 case MCSClient::QUEUED: 100 return GCMClient::SUCCESS; 101 case MCSClient::QUEUE_SIZE_LIMIT_REACHED: 102 return GCMClient::NETWORK_ERROR; 103 case MCSClient::APP_QUEUE_SIZE_LIMIT_REACHED: 104 return GCMClient::NETWORK_ERROR; 105 case MCSClient::MESSAGE_TOO_LARGE: 106 return GCMClient::INVALID_PARAMETER; 107 case MCSClient::NO_CONNECTION_ON_ZERO_TTL: 108 return GCMClient::NETWORK_ERROR; 109 case MCSClient::TTL_EXCEEDED: 110 return GCMClient::NETWORK_ERROR; 111 case MCSClient::SENT: 112 default: 113 NOTREACHED(); 114 break; 115 } 116 return GCMClientImpl::UNKNOWN_ERROR; 117} 118 119void ToCheckinProtoVersion( 120 const GCMClient::ChromeBuildInfo& chrome_build_info, 121 checkin_proto::ChromeBuildProto* android_build_info) { 122 checkin_proto::ChromeBuildProto_Platform platform; 123 switch (chrome_build_info.platform) { 124 case GCMClient::PLATFORM_WIN: 125 platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_WIN; 126 break; 127 case GCMClient::PLATFORM_MAC: 128 platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_MAC; 129 break; 130 case GCMClient::PLATFORM_LINUX: 131 platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_LINUX; 132 break; 133 case GCMClient::PLATFORM_IOS: 134 platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_IOS; 135 break; 136 case GCMClient::PLATFORM_ANDROID: 137 platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_ANDROID; 138 break; 139 case GCMClient::PLATFORM_CROS: 140 platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_CROS; 141 break; 142 case GCMClient::PLATFORM_UNKNOWN: 143 // For unknown platform, return as LINUX. 144 platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_LINUX; 145 break; 146 default: 147 NOTREACHED(); 148 platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_LINUX; 149 break; 150 } 151 android_build_info->set_platform(platform); 152 153 checkin_proto::ChromeBuildProto_Channel channel; 154 switch (chrome_build_info.channel) { 155 case GCMClient::CHANNEL_STABLE: 156 channel = checkin_proto::ChromeBuildProto_Channel_CHANNEL_STABLE; 157 break; 158 case GCMClient::CHANNEL_BETA: 159 channel = checkin_proto::ChromeBuildProto_Channel_CHANNEL_BETA; 160 break; 161 case GCMClient::CHANNEL_DEV: 162 channel = checkin_proto::ChromeBuildProto_Channel_CHANNEL_DEV; 163 break; 164 case GCMClient::CHANNEL_CANARY: 165 channel = checkin_proto::ChromeBuildProto_Channel_CHANNEL_CANARY; 166 break; 167 case GCMClient::CHANNEL_UNKNOWN: 168 channel = checkin_proto::ChromeBuildProto_Channel_CHANNEL_UNKNOWN; 169 break; 170 default: 171 NOTREACHED(); 172 channel = checkin_proto::ChromeBuildProto_Channel_CHANNEL_UNKNOWN; 173 break; 174 } 175 android_build_info->set_channel(channel); 176 177 android_build_info->set_chrome_version(chrome_build_info.version); 178} 179 180MessageType DecodeMessageType(const std::string& value) { 181 if (kMessageTypeDeletedMessagesKey == value) 182 return DELETED_MESSAGES; 183 if (kMessageTypeSendErrorKey == value) 184 return SEND_ERROR; 185 if (kMessageTypeDataMessage == value) 186 return DATA_MESSAGE; 187 return UNKNOWN; 188} 189 190void RecordOutgoingMessageToUMA( 191 const gcm::GCMClient::OutgoingMessage& message) { 192 OutgoingMessageTTLCategory ttl_category; 193 if (message.time_to_live == 0) 194 ttl_category = TTL_ZERO; 195 else if (message.time_to_live <= 60 ) 196 ttl_category = TTL_LESS_THAN_OR_EQUAL_TO_ONE_MINUTE; 197 else if (message.time_to_live <= 60 * 60) 198 ttl_category = TTL_LESS_THAN_OR_EQUAL_TO_ONE_HOUR; 199 else if (message.time_to_live <= 24 * 60 * 60) 200 ttl_category = TTL_LESS_THAN_OR_EQUAL_TO_ONE_DAY; 201 else if (message.time_to_live <= 7 * 24 * 60 * 60) 202 ttl_category = TTL_LESS_THAN_OR_EQUAL_TO_ONE_WEEK; 203 else if (message.time_to_live < gcm::GCMClient::OutgoingMessage::kMaximumTTL) 204 ttl_category = TTL_MORE_THAN_ONE_WEEK; 205 else 206 ttl_category = TTL_MAXIMUM; 207 208 UMA_HISTOGRAM_ENUMERATION("GCM.GCMOutgoingMessageTTLCategory", 209 ttl_category, 210 TTL_CATEGORY_COUNT); 211} 212 213} // namespace 214 215GCMInternalsBuilder::GCMInternalsBuilder() {} 216GCMInternalsBuilder::~GCMInternalsBuilder() {} 217 218scoped_ptr<base::Clock> GCMInternalsBuilder::BuildClock() { 219 return make_scoped_ptr<base::Clock>(new base::DefaultClock()); 220} 221 222scoped_ptr<MCSClient> GCMInternalsBuilder::BuildMCSClient( 223 const std::string& version, 224 base::Clock* clock, 225 ConnectionFactory* connection_factory, 226 GCMStore* gcm_store, 227 GCMStatsRecorder* recorder) { 228 return make_scoped_ptr<MCSClient>( 229 new MCSClient(version, 230 clock, 231 connection_factory, 232 gcm_store, 233 recorder)); 234} 235 236scoped_ptr<ConnectionFactory> GCMInternalsBuilder::BuildConnectionFactory( 237 const std::vector<GURL>& endpoints, 238 const net::BackoffEntry::Policy& backoff_policy, 239 scoped_refptr<net::HttpNetworkSession> network_session, 240 net::NetLog* net_log, 241 GCMStatsRecorder* recorder) { 242 return make_scoped_ptr<ConnectionFactory>( 243 new ConnectionFactoryImpl(endpoints, 244 backoff_policy, 245 network_session, 246 net_log, 247 recorder)); 248} 249 250GCMClientImpl::GCMClientImpl(scoped_ptr<GCMInternalsBuilder> internals_builder) 251 : internals_builder_(internals_builder.Pass()), 252 state_(UNINITIALIZED), 253 delegate_(NULL), 254 clock_(internals_builder_->BuildClock()), 255 url_request_context_getter_(NULL), 256 pending_registration_requests_deleter_(&pending_registration_requests_), 257 pending_unregistration_requests_deleter_( 258 &pending_unregistration_requests_), 259 periodic_checkin_ptr_factory_(this), 260 weak_ptr_factory_(this) { 261} 262 263GCMClientImpl::~GCMClientImpl() { 264} 265 266void GCMClientImpl::Initialize( 267 const ChromeBuildInfo& chrome_build_info, 268 const base::FilePath& path, 269 const scoped_refptr<base::SequencedTaskRunner>& blocking_task_runner, 270 const scoped_refptr<net::URLRequestContextGetter>& 271 url_request_context_getter, 272 scoped_ptr<Encryptor> encryptor, 273 GCMClient::Delegate* delegate) { 274 DCHECK_EQ(UNINITIALIZED, state_); 275 DCHECK(url_request_context_getter); 276 DCHECK(delegate); 277 278 url_request_context_getter_ = url_request_context_getter; 279 const net::HttpNetworkSession::Params* network_session_params = 280 url_request_context_getter_->GetURLRequestContext()-> 281 GetNetworkSessionParams(); 282 DCHECK(network_session_params); 283 network_session_ = new net::HttpNetworkSession(*network_session_params); 284 285 chrome_build_info_ = chrome_build_info; 286 287 gcm_store_.reset( 288 new GCMStoreImpl(path, blocking_task_runner, encryptor.Pass())); 289 290 delegate_ = delegate; 291 292 recorder_.SetDelegate(this); 293 294 state_ = INITIALIZED; 295} 296 297void GCMClientImpl::Start() { 298 DCHECK_EQ(INITIALIZED, state_); 299 300 // Once the loading is completed, the check-in will be initiated. 301 gcm_store_->Load(base::Bind(&GCMClientImpl::OnLoadCompleted, 302 weak_ptr_factory_.GetWeakPtr())); 303 state_ = LOADING; 304} 305 306void GCMClientImpl::OnLoadCompleted(scoped_ptr<GCMStore::LoadResult> result) { 307 DCHECK_EQ(LOADING, state_); 308 309 if (!result->success) { 310 ResetState(); 311 return; 312 } 313 314 registrations_ = result->registrations; 315 device_checkin_info_.android_id = result->device_android_id; 316 device_checkin_info_.secret = result->device_security_token; 317 last_checkin_time_ = result->last_checkin_time; 318 gservices_settings_.UpdateFromLoadResult(*result); 319 InitializeMCSClient(result.Pass()); 320 321 if (device_checkin_info_.IsValid()) { 322 SchedulePeriodicCheckin(); 323 OnReady(); 324 return; 325 } 326 327 state_ = INITIAL_DEVICE_CHECKIN; 328 device_checkin_info_.Reset(); 329 StartCheckin(); 330} 331 332void GCMClientImpl::InitializeMCSClient( 333 scoped_ptr<GCMStore::LoadResult> result) { 334 std::vector<GURL> endpoints; 335 endpoints.push_back(gservices_settings_.GetMCSMainEndpoint()); 336 endpoints.push_back(gservices_settings_.GetMCSFallbackEndpoint()); 337 connection_factory_ = internals_builder_->BuildConnectionFactory( 338 endpoints, 339 kDefaultBackoffPolicy, 340 network_session_, 341 net_log_.net_log(), 342 &recorder_); 343 connection_factory_->SetConnectionListener(this); 344 mcs_client_ = internals_builder_->BuildMCSClient( 345 chrome_build_info_.version, 346 clock_.get(), 347 connection_factory_.get(), 348 gcm_store_.get(), 349 &recorder_).Pass(); 350 351 mcs_client_->Initialize( 352 base::Bind(&GCMClientImpl::OnMCSError, weak_ptr_factory_.GetWeakPtr()), 353 base::Bind(&GCMClientImpl::OnMessageReceivedFromMCS, 354 weak_ptr_factory_.GetWeakPtr()), 355 base::Bind(&GCMClientImpl::OnMessageSentToMCS, 356 weak_ptr_factory_.GetWeakPtr()), 357 result.Pass()); 358} 359 360void GCMClientImpl::OnFirstTimeDeviceCheckinCompleted( 361 const CheckinInfo& checkin_info) { 362 DCHECK(!device_checkin_info_.IsValid()); 363 364 device_checkin_info_.android_id = checkin_info.android_id; 365 device_checkin_info_.secret = checkin_info.secret; 366 gcm_store_->SetDeviceCredentials( 367 checkin_info.android_id, checkin_info.secret, 368 base::Bind(&GCMClientImpl::SetDeviceCredentialsCallback, 369 weak_ptr_factory_.GetWeakPtr())); 370 371 OnReady(); 372} 373 374void GCMClientImpl::OnReady() { 375 state_ = READY; 376 StartMCSLogin(); 377 378 delegate_->OnGCMReady(); 379} 380 381void GCMClientImpl::StartMCSLogin() { 382 DCHECK_EQ(READY, state_); 383 DCHECK(device_checkin_info_.IsValid()); 384 mcs_client_->Login(device_checkin_info_.android_id, 385 device_checkin_info_.secret); 386} 387 388void GCMClientImpl::ResetState() { 389 state_ = UNINITIALIZED; 390 // TODO(fgorski): reset all of the necessart objects and start over. 391} 392 393void GCMClientImpl::StartCheckin() { 394 // Make sure no checkin is in progress. 395 if (checkin_request_.get()) 396 return; 397 398 checkin_proto::ChromeBuildProto chrome_build_proto; 399 ToCheckinProtoVersion(chrome_build_info_, &chrome_build_proto); 400 CheckinRequest::RequestInfo request_info(device_checkin_info_.android_id, 401 device_checkin_info_.secret, 402 gservices_settings_.digest(), 403 chrome_build_proto); 404 checkin_request_.reset( 405 new CheckinRequest(gservices_settings_.GetCheckinURL(), 406 request_info, 407 kDefaultBackoffPolicy, 408 base::Bind(&GCMClientImpl::OnCheckinCompleted, 409 weak_ptr_factory_.GetWeakPtr()), 410 url_request_context_getter_, 411 &recorder_)); 412 checkin_request_->Start(); 413} 414 415void GCMClientImpl::OnCheckinCompleted( 416 const checkin_proto::AndroidCheckinResponse& checkin_response) { 417 checkin_request_.reset(); 418 419 if (!checkin_response.has_android_id() || 420 !checkin_response.has_security_token()) { 421 // TODO(fgorski): I don't think a retry here will help, we should probably 422 // start over. By checking in with (0, 0). 423 return; 424 } 425 426 CheckinInfo checkin_info; 427 checkin_info.android_id = checkin_response.android_id(); 428 checkin_info.secret = checkin_response.security_token(); 429 430 if (state_ == INITIAL_DEVICE_CHECKIN) { 431 OnFirstTimeDeviceCheckinCompleted(checkin_info); 432 } else { 433 // checkin_info is not expected to change after a periodic checkin as it 434 // would invalidate the registratoin IDs. 435 DCHECK_EQ(READY, state_); 436 DCHECK_EQ(device_checkin_info_.android_id, checkin_info.android_id); 437 DCHECK_EQ(device_checkin_info_.secret, checkin_info.secret); 438 } 439 440 if (device_checkin_info_.IsValid()) { 441 // First update G-services settings, as something might have changed. 442 if (gservices_settings_.UpdateFromCheckinResponse(checkin_response)) { 443 gcm_store_->SetGServicesSettings( 444 gservices_settings_.settings_map(), 445 gservices_settings_.digest(), 446 base::Bind(&GCMClientImpl::SetGServicesSettingsCallback, 447 weak_ptr_factory_.GetWeakPtr())); 448 } 449 450 last_checkin_time_ = clock_->Now(); 451 gcm_store_->SetLastCheckinTime( 452 last_checkin_time_, 453 base::Bind(&GCMClientImpl::SetLastCheckinTimeCallback, 454 weak_ptr_factory_.GetWeakPtr())); 455 SchedulePeriodicCheckin(); 456 } 457} 458 459void GCMClientImpl::SetGServicesSettingsCallback(bool success) { 460 DCHECK(success); 461} 462 463void GCMClientImpl::SchedulePeriodicCheckin() { 464 // Make sure no checkin is in progress. 465 if (checkin_request_.get()) 466 return; 467 468 // There should be only one periodic checkin pending at a time. Removing 469 // pending periodic checkin to schedule a new one. 470 periodic_checkin_ptr_factory_.InvalidateWeakPtrs(); 471 472 base::TimeDelta time_to_next_checkin = GetTimeToNextCheckin(); 473 if (time_to_next_checkin < base::TimeDelta()) 474 time_to_next_checkin = base::TimeDelta(); 475 476 base::MessageLoop::current()->PostDelayedTask( 477 FROM_HERE, 478 base::Bind(&GCMClientImpl::StartCheckin, 479 periodic_checkin_ptr_factory_.GetWeakPtr()), 480 time_to_next_checkin); 481} 482 483base::TimeDelta GCMClientImpl::GetTimeToNextCheckin() const { 484 return last_checkin_time_ + gservices_settings_.GetCheckinInterval() - 485 clock_->Now(); 486} 487 488void GCMClientImpl::SetLastCheckinTimeCallback(bool success) { 489 // TODO(fgorski): This is one of the signals that store needs a rebuild. 490 DCHECK(success); 491} 492 493void GCMClientImpl::SetDeviceCredentialsCallback(bool success) { 494 // TODO(fgorski): This is one of the signals that store needs a rebuild. 495 DCHECK(success); 496} 497 498void GCMClientImpl::UpdateRegistrationCallback(bool success) { 499 // TODO(fgorski): This is one of the signals that store needs a rebuild. 500 DCHECK(success); 501} 502 503void GCMClientImpl::Stop() { 504 weak_ptr_factory_.InvalidateWeakPtrs(); 505 device_checkin_info_.Reset(); 506 connection_factory_.reset(); 507 delegate_->OnDisconnected(); 508 mcs_client_.reset(); 509 checkin_request_.reset(); 510 pending_registration_requests_.clear(); 511 state_ = INITIALIZED; 512 gcm_store_->Close(); 513} 514 515void GCMClientImpl::CheckOut() { 516 Stop(); 517 gcm_store_->Destroy(base::Bind(&GCMClientImpl::OnGCMStoreDestroyed, 518 weak_ptr_factory_.GetWeakPtr())); 519} 520 521void GCMClientImpl::Register(const std::string& app_id, 522 const std::vector<std::string>& sender_ids) { 523 DCHECK_EQ(state_, READY); 524 525 // If the same sender ids is provided, return the cached registration ID 526 // directly. 527 RegistrationInfoMap::const_iterator registrations_iter = 528 registrations_.find(app_id); 529 if (registrations_iter != registrations_.end() && 530 registrations_iter->second->sender_ids == sender_ids) { 531 delegate_->OnRegisterFinished( 532 app_id, registrations_iter->second->registration_id, SUCCESS); 533 return; 534 } 535 536 RegistrationRequest::RequestInfo request_info( 537 device_checkin_info_.android_id, 538 device_checkin_info_.secret, 539 app_id, 540 sender_ids); 541 DCHECK_EQ(0u, pending_registration_requests_.count(app_id)); 542 543 RegistrationRequest* registration_request = 544 new RegistrationRequest(gservices_settings_.GetRegistrationURL(), 545 request_info, 546 kDefaultBackoffPolicy, 547 base::Bind(&GCMClientImpl::OnRegisterCompleted, 548 weak_ptr_factory_.GetWeakPtr(), 549 app_id, 550 sender_ids), 551 kMaxRegistrationRetries, 552 url_request_context_getter_, 553 &recorder_); 554 pending_registration_requests_[app_id] = registration_request; 555 registration_request->Start(); 556} 557 558void GCMClientImpl::OnRegisterCompleted( 559 const std::string& app_id, 560 const std::vector<std::string>& sender_ids, 561 RegistrationRequest::Status status, 562 const std::string& registration_id) { 563 DCHECK(delegate_); 564 565 Result result; 566 PendingRegistrationRequests::iterator iter = 567 pending_registration_requests_.find(app_id); 568 if (iter == pending_registration_requests_.end()) 569 result = UNKNOWN_ERROR; 570 else if (status == RegistrationRequest::INVALID_SENDER) 571 result = INVALID_PARAMETER; 572 else if (registration_id.empty()) 573 result = SERVER_ERROR; 574 else 575 result = SUCCESS; 576 577 if (result == SUCCESS) { 578 // Cache it. 579 linked_ptr<RegistrationInfo> registration(new RegistrationInfo); 580 registration->sender_ids = sender_ids; 581 registration->registration_id = registration_id; 582 registrations_[app_id] = registration; 583 584 // Save it in the persistent store. 585 gcm_store_->AddRegistration( 586 app_id, 587 registration, 588 base::Bind(&GCMClientImpl::UpdateRegistrationCallback, 589 weak_ptr_factory_.GetWeakPtr())); 590 } 591 592 delegate_->OnRegisterFinished( 593 app_id, result == SUCCESS ? registration_id : std::string(), result); 594 595 if (iter != pending_registration_requests_.end()) { 596 delete iter->second; 597 pending_registration_requests_.erase(iter); 598 } 599} 600 601void GCMClientImpl::Unregister(const std::string& app_id) { 602 DCHECK_EQ(state_, READY); 603 if (pending_unregistration_requests_.count(app_id) == 1) 604 return; 605 606 // Remove from the cache and persistent store. 607 registrations_.erase(app_id); 608 gcm_store_->RemoveRegistration( 609 app_id, 610 base::Bind(&GCMClientImpl::UpdateRegistrationCallback, 611 weak_ptr_factory_.GetWeakPtr())); 612 613 UnregistrationRequest::RequestInfo request_info( 614 device_checkin_info_.android_id, 615 device_checkin_info_.secret, 616 app_id); 617 618 UnregistrationRequest* unregistration_request = new UnregistrationRequest( 619 gservices_settings_.GetRegistrationURL(), 620 request_info, 621 kDefaultBackoffPolicy, 622 base::Bind(&GCMClientImpl::OnUnregisterCompleted, 623 weak_ptr_factory_.GetWeakPtr(), 624 app_id), 625 url_request_context_getter_, 626 &recorder_); 627 pending_unregistration_requests_[app_id] = unregistration_request; 628 unregistration_request->Start(); 629} 630 631void GCMClientImpl::OnUnregisterCompleted( 632 const std::string& app_id, 633 UnregistrationRequest::Status status) { 634 DVLOG(1) << "Unregister completed for app: " << app_id 635 << " with " << (status ? "success." : "failure."); 636 delegate_->OnUnregisterFinished( 637 app_id, 638 status == UnregistrationRequest::SUCCESS ? SUCCESS : SERVER_ERROR); 639 640 PendingUnregistrationRequests::iterator iter = 641 pending_unregistration_requests_.find(app_id); 642 if (iter == pending_unregistration_requests_.end()) 643 return; 644 645 delete iter->second; 646 pending_unregistration_requests_.erase(iter); 647} 648 649void GCMClientImpl::OnGCMStoreDestroyed(bool success) { 650 DLOG_IF(ERROR, !success) << "GCM store failed to be destroyed!"; 651 UMA_HISTOGRAM_BOOLEAN("GCM.StoreDestroySucceeded", success); 652} 653 654void GCMClientImpl::Send(const std::string& app_id, 655 const std::string& receiver_id, 656 const OutgoingMessage& message) { 657 DCHECK_EQ(state_, READY); 658 659 RecordOutgoingMessageToUMA(message); 660 661 mcs_proto::DataMessageStanza stanza; 662 stanza.set_ttl(message.time_to_live); 663 stanza.set_sent(clock_->Now().ToInternalValue() / 664 base::Time::kMicrosecondsPerSecond); 665 stanza.set_id(message.id); 666 stanza.set_from(kSendMessageFromValue); 667 stanza.set_to(receiver_id); 668 stanza.set_category(app_id); 669 670 for (MessageData::const_iterator iter = message.data.begin(); 671 iter != message.data.end(); 672 ++iter) { 673 mcs_proto::AppData* app_data = stanza.add_app_data(); 674 app_data->set_key(iter->first); 675 app_data->set_value(iter->second); 676 } 677 678 MCSMessage mcs_message(stanza); 679 DVLOG(1) << "MCS message size: " << mcs_message.size(); 680 mcs_client_->SendMessage(mcs_message); 681} 682 683std::string GCMClientImpl::GetStateString() const { 684 switch(state_) { 685 case GCMClientImpl::INITIALIZED: 686 return "INITIALIZED"; 687 case GCMClientImpl::UNINITIALIZED: 688 return "UNINITIALIZED"; 689 case GCMClientImpl::LOADING: 690 return "LOADING"; 691 case GCMClientImpl::INITIAL_DEVICE_CHECKIN: 692 return "INITIAL_DEVICE_CHECKIN"; 693 case GCMClientImpl::READY: 694 return "READY"; 695 default: 696 NOTREACHED(); 697 return std::string(); 698 } 699} 700 701void GCMClientImpl::SetRecording(bool recording) { 702 recorder_.SetRecording(recording); 703} 704 705void GCMClientImpl::ClearActivityLogs() { 706 recorder_.Clear(); 707} 708 709GCMClient::GCMStatistics GCMClientImpl::GetStatistics() const { 710 GCMClient::GCMStatistics stats; 711 stats.gcm_client_created = true; 712 stats.is_recording = recorder_.is_recording(); 713 stats.gcm_client_state = GetStateString(); 714 stats.connection_client_created = mcs_client_.get() != NULL; 715 if (connection_factory_.get()) 716 stats.connection_state = connection_factory_->GetConnectionStateString(); 717 if (mcs_client_.get()) { 718 stats.send_queue_size = mcs_client_->GetSendQueueSize(); 719 stats.resend_queue_size = mcs_client_->GetResendQueueSize(); 720 } 721 if (device_checkin_info_.android_id > 0) 722 stats.android_id = device_checkin_info_.android_id; 723 recorder_.CollectActivities(&stats.recorded_activities); 724 725 for (RegistrationInfoMap::const_iterator it = registrations_.begin(); 726 it != registrations_.end(); ++it) { 727 stats.registered_app_ids.push_back(it->first); 728 } 729 return stats; 730} 731 732void GCMClientImpl::OnActivityRecorded() { 733 delegate_->OnActivityRecorded(); 734} 735 736void GCMClientImpl::OnConnected(const GURL& current_server, 737 const net::IPEndPoint& ip_endpoint) { 738 // TODO(gcm): expose current server in debug page. 739 delegate_->OnActivityRecorded(); 740 delegate_->OnConnected(ip_endpoint); 741} 742 743void GCMClientImpl::OnDisconnected() { 744 delegate_->OnActivityRecorded(); 745 delegate_->OnDisconnected(); 746} 747 748void GCMClientImpl::OnMessageReceivedFromMCS(const gcm::MCSMessage& message) { 749 switch (message.tag()) { 750 case kLoginResponseTag: 751 DVLOG(1) << "Login response received by GCM Client. Ignoring."; 752 return; 753 case kDataMessageStanzaTag: 754 DVLOG(1) << "A downstream message received. Processing..."; 755 HandleIncomingMessage(message); 756 return; 757 default: 758 NOTREACHED() << "Message with unexpected tag received by GCMClient"; 759 return; 760 } 761} 762 763void GCMClientImpl::OnMessageSentToMCS(int64 user_serial_number, 764 const std::string& app_id, 765 const std::string& message_id, 766 MCSClient::MessageSendStatus status) { 767 DCHECK_EQ(user_serial_number, kDefaultUserSerialNumber); 768 DCHECK(delegate_); 769 770 // TTL_EXCEEDED is singled out here, because it can happen long time after the 771 // message was sent. That is why it comes as |OnMessageSendError| event rather 772 // than |OnSendFinished|. SendErrorDetails.additional_data is left empty. 773 // All other errors will be raised immediately, through asynchronous callback. 774 // It is expected that TTL_EXCEEDED will be issued for a message that was 775 // previously issued |OnSendFinished| with status SUCCESS. 776 // For now, we do not report that the message has been sent and acked 777 // successfully. 778 // TODO(jianli): Consider adding UMA for this status. 779 if (status == MCSClient::TTL_EXCEEDED) { 780 SendErrorDetails send_error_details; 781 send_error_details.message_id = message_id; 782 send_error_details.result = GCMClient::TTL_EXCEEDED; 783 delegate_->OnMessageSendError(app_id, send_error_details); 784 } else if (status != MCSClient::SENT) { 785 delegate_->OnSendFinished(app_id, message_id, ToGCMClientResult(status)); 786 } 787} 788 789void GCMClientImpl::OnMCSError() { 790 // TODO(fgorski): For now it replaces the initialization method. Long term it 791 // should have an error or status passed in. 792} 793 794void GCMClientImpl::HandleIncomingMessage(const gcm::MCSMessage& message) { 795 DCHECK(delegate_); 796 797 const mcs_proto::DataMessageStanza& data_message_stanza = 798 reinterpret_cast<const mcs_proto::DataMessageStanza&>( 799 message.GetProtobuf()); 800 DCHECK_EQ(data_message_stanza.device_user_id(), kDefaultUserSerialNumber); 801 802 // Copying all the data from the stanza to a MessageData object. When present, 803 // keys like kMessageTypeKey or kSendErrorMessageIdKey will be filtered out 804 // later. 805 MessageData message_data; 806 for (int i = 0; i < data_message_stanza.app_data_size(); ++i) { 807 std::string key = data_message_stanza.app_data(i).key(); 808 message_data[key] = data_message_stanza.app_data(i).value(); 809 } 810 811 MessageType message_type = DATA_MESSAGE; 812 MessageData::iterator iter = message_data.find(kMessageTypeKey); 813 if (iter != message_data.end()) { 814 message_type = DecodeMessageType(iter->second); 815 message_data.erase(iter); 816 } 817 818 switch (message_type) { 819 case DATA_MESSAGE: 820 HandleIncomingDataMessage(data_message_stanza, message_data); 821 break; 822 case DELETED_MESSAGES: 823 recorder_.RecordDataMessageReceived(data_message_stanza.category(), 824 data_message_stanza.from(), 825 data_message_stanza.ByteSize(), 826 true, 827 GCMStatsRecorder::DELETED_MESSAGES); 828 delegate_->OnMessagesDeleted(data_message_stanza.category()); 829 break; 830 case SEND_ERROR: 831 HandleIncomingSendError(data_message_stanza, message_data); 832 break; 833 case UNKNOWN: 834 default: // Treat default the same as UNKNOWN. 835 DVLOG(1) << "Unknown message_type received. Message ignored. " 836 << "App ID: " << data_message_stanza.category() << "."; 837 break; 838 } 839} 840 841void GCMClientImpl::HandleIncomingDataMessage( 842 const mcs_proto::DataMessageStanza& data_message_stanza, 843 MessageData& message_data) { 844 std::string app_id = data_message_stanza.category(); 845 846 // Drop the message when the app is not registered for the sender of the 847 // message. 848 RegistrationInfoMap::iterator iter = registrations_.find(app_id); 849 bool not_registered = 850 iter == registrations_.end() || 851 std::find(iter->second->sender_ids.begin(), 852 iter->second->sender_ids.end(), 853 data_message_stanza.from()) == iter->second->sender_ids.end(); 854 recorder_.RecordDataMessageReceived(app_id, data_message_stanza.from(), 855 data_message_stanza.ByteSize(), !not_registered, 856 GCMStatsRecorder::DATA_MESSAGE); 857 if (not_registered) { 858 return; 859 } 860 861 IncomingMessage incoming_message; 862 incoming_message.sender_id = data_message_stanza.from(); 863 if (data_message_stanza.has_token()) 864 incoming_message.collapse_key = data_message_stanza.token(); 865 incoming_message.data = message_data; 866 delegate_->OnMessageReceived(app_id, incoming_message); 867} 868 869void GCMClientImpl::HandleIncomingSendError( 870 const mcs_proto::DataMessageStanza& data_message_stanza, 871 MessageData& message_data) { 872 SendErrorDetails send_error_details; 873 send_error_details.additional_data = message_data; 874 send_error_details.result = SERVER_ERROR; 875 876 MessageData::iterator iter = 877 send_error_details.additional_data.find(kSendErrorMessageIdKey); 878 if (iter != send_error_details.additional_data.end()) { 879 send_error_details.message_id = iter->second; 880 send_error_details.additional_data.erase(iter); 881 } 882 883 recorder_.RecordIncomingSendError( 884 data_message_stanza.category(), 885 data_message_stanza.to(), 886 data_message_stanza.id()); 887 delegate_->OnMessageSendError(data_message_stanza.category(), 888 send_error_details); 889} 890 891} // namespace gcm 892