| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/gcm_driver/gcm_client_impl.h" |
| |
| #include "base/bind.h" |
| #include "base/files/file_path.h" |
| #include "base/logging.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/metrics/histogram.h" |
| #include "base/sequenced_task_runner.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/time/default_clock.h" |
| #include "google_apis/gcm/base/encryptor.h" |
| #include "google_apis/gcm/base/mcs_message.h" |
| #include "google_apis/gcm/base/mcs_util.h" |
| #include "google_apis/gcm/engine/checkin_request.h" |
| #include "google_apis/gcm/engine/connection_factory_impl.h" |
| #include "google_apis/gcm/engine/gcm_store_impl.h" |
| #include "google_apis/gcm/monitoring/gcm_stats_recorder.h" |
| #include "google_apis/gcm/protocol/checkin.pb.h" |
| #include "google_apis/gcm/protocol/mcs.pb.h" |
| #include "net/http/http_network_session.h" |
| #include "net/url_request/url_request_context.h" |
| #include "url/gurl.h" |
| |
| namespace gcm { |
| |
| namespace { |
| |
| // Backoff policy. Shared across reconnection logic and checkin/(un)registration |
| // retries. |
| // Note: In order to ensure a minimum of 20 seconds between server errors (for |
| // server reasons), we have a 30s +- 10s (33%) jitter initial backoff. |
| // TODO(zea): consider sharing/synchronizing the scheduling of backoff retries |
| // themselves. |
| const net::BackoffEntry::Policy kDefaultBackoffPolicy = { |
| // Number of initial errors (in sequence) to ignore before applying |
| // exponential back-off rules. |
| 0, |
| |
| // Initial delay for exponential back-off in ms. |
| 30 * 1000, // 30 seconds. |
| |
| // Factor by which the waiting time will be multiplied. |
| 2, |
| |
| // Fuzzing percentage. ex: 10% will spread requests randomly |
| // between 90%-100% of the calculated time. |
| 0.33, // 33%. |
| |
| // Maximum amount of time we are willing to delay our request in ms. |
| 10 * 60 * 1000, // 10 minutes. |
| |
| // Time to keep an entry from being discarded even when it |
| // has no significant state, -1 to never discard. |
| -1, |
| |
| // Don't use initial delay unless the last request was an error. |
| false, |
| }; |
| |
| // Indicates a message type of the received message. |
| enum MessageType { |
| UNKNOWN, // Undetermined type. |
| DATA_MESSAGE, // Regular data message. |
| DELETED_MESSAGES, // Messages were deleted on the server. |
| SEND_ERROR, // Error sending a message. |
| }; |
| |
| enum OutgoingMessageTTLCategory { |
| TTL_ZERO, |
| TTL_LESS_THAN_OR_EQUAL_TO_ONE_MINUTE, |
| TTL_LESS_THAN_OR_EQUAL_TO_ONE_HOUR, |
| TTL_LESS_THAN_OR_EQUAL_TO_ONE_DAY, |
| TTL_LESS_THAN_OR_EQUAL_TO_ONE_WEEK, |
| TTL_MORE_THAN_ONE_WEEK, |
| TTL_MAXIMUM, |
| // NOTE: always keep this entry at the end. Add new TTL category only |
| // immediately above this line. Make sure to update the corresponding |
| // histogram enum accordingly. |
| TTL_CATEGORY_COUNT |
| }; |
| |
| const int kMaxRegistrationRetries = 5; |
| const char kMessageTypeDataMessage[] = "gcm"; |
| const char kMessageTypeDeletedMessagesKey[] = "deleted_messages"; |
| const char kMessageTypeKey[] = "message_type"; |
| const char kMessageTypeSendErrorKey[] = "send_error"; |
| const char kSendErrorMessageIdKey[] = "google.message_id"; |
| const char kSendMessageFromValue[] = "gcm@chrome.com"; |
| const int64 kDefaultUserSerialNumber = 0LL; |
| |
| GCMClient::Result ToGCMClientResult(MCSClient::MessageSendStatus status) { |
| switch (status) { |
| case MCSClient::QUEUED: |
| return GCMClient::SUCCESS; |
| case MCSClient::QUEUE_SIZE_LIMIT_REACHED: |
| return GCMClient::NETWORK_ERROR; |
| case MCSClient::APP_QUEUE_SIZE_LIMIT_REACHED: |
| return GCMClient::NETWORK_ERROR; |
| case MCSClient::MESSAGE_TOO_LARGE: |
| return GCMClient::INVALID_PARAMETER; |
| case MCSClient::NO_CONNECTION_ON_ZERO_TTL: |
| return GCMClient::NETWORK_ERROR; |
| case MCSClient::TTL_EXCEEDED: |
| return GCMClient::NETWORK_ERROR; |
| case MCSClient::SENT: |
| default: |
| NOTREACHED(); |
| break; |
| } |
| return GCMClientImpl::UNKNOWN_ERROR; |
| } |
| |
| void ToCheckinProtoVersion( |
| const GCMClient::ChromeBuildInfo& chrome_build_info, |
| checkin_proto::ChromeBuildProto* android_build_info) { |
| checkin_proto::ChromeBuildProto_Platform platform; |
| switch (chrome_build_info.platform) { |
| case GCMClient::PLATFORM_WIN: |
| platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_WIN; |
| break; |
| case GCMClient::PLATFORM_MAC: |
| platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_MAC; |
| break; |
| case GCMClient::PLATFORM_LINUX: |
| platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_LINUX; |
| break; |
| case GCMClient::PLATFORM_IOS: |
| platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_IOS; |
| break; |
| case GCMClient::PLATFORM_ANDROID: |
| platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_ANDROID; |
| break; |
| case GCMClient::PLATFORM_CROS: |
| platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_CROS; |
| break; |
| case GCMClient::PLATFORM_UNKNOWN: |
| // For unknown platform, return as LINUX. |
| platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_LINUX; |
| break; |
| default: |
| NOTREACHED(); |
| platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_LINUX; |
| break; |
| } |
| android_build_info->set_platform(platform); |
| |
| checkin_proto::ChromeBuildProto_Channel channel; |
| switch (chrome_build_info.channel) { |
| case GCMClient::CHANNEL_STABLE: |
| channel = checkin_proto::ChromeBuildProto_Channel_CHANNEL_STABLE; |
| break; |
| case GCMClient::CHANNEL_BETA: |
| channel = checkin_proto::ChromeBuildProto_Channel_CHANNEL_BETA; |
| break; |
| case GCMClient::CHANNEL_DEV: |
| channel = checkin_proto::ChromeBuildProto_Channel_CHANNEL_DEV; |
| break; |
| case GCMClient::CHANNEL_CANARY: |
| channel = checkin_proto::ChromeBuildProto_Channel_CHANNEL_CANARY; |
| break; |
| case GCMClient::CHANNEL_UNKNOWN: |
| channel = checkin_proto::ChromeBuildProto_Channel_CHANNEL_UNKNOWN; |
| break; |
| default: |
| NOTREACHED(); |
| channel = checkin_proto::ChromeBuildProto_Channel_CHANNEL_UNKNOWN; |
| break; |
| } |
| android_build_info->set_channel(channel); |
| |
| android_build_info->set_chrome_version(chrome_build_info.version); |
| } |
| |
| MessageType DecodeMessageType(const std::string& value) { |
| if (kMessageTypeDeletedMessagesKey == value) |
| return DELETED_MESSAGES; |
| if (kMessageTypeSendErrorKey == value) |
| return SEND_ERROR; |
| if (kMessageTypeDataMessage == value) |
| return DATA_MESSAGE; |
| return UNKNOWN; |
| } |
| |
| void RecordOutgoingMessageToUMA( |
| const gcm::GCMClient::OutgoingMessage& message) { |
| OutgoingMessageTTLCategory ttl_category; |
| if (message.time_to_live == 0) |
| ttl_category = TTL_ZERO; |
| else if (message.time_to_live <= 60 ) |
| ttl_category = TTL_LESS_THAN_OR_EQUAL_TO_ONE_MINUTE; |
| else if (message.time_to_live <= 60 * 60) |
| ttl_category = TTL_LESS_THAN_OR_EQUAL_TO_ONE_HOUR; |
| else if (message.time_to_live <= 24 * 60 * 60) |
| ttl_category = TTL_LESS_THAN_OR_EQUAL_TO_ONE_DAY; |
| else if (message.time_to_live <= 7 * 24 * 60 * 60) |
| ttl_category = TTL_LESS_THAN_OR_EQUAL_TO_ONE_WEEK; |
| else if (message.time_to_live < gcm::GCMClient::OutgoingMessage::kMaximumTTL) |
| ttl_category = TTL_MORE_THAN_ONE_WEEK; |
| else |
| ttl_category = TTL_MAXIMUM; |
| |
| UMA_HISTOGRAM_ENUMERATION("GCM.GCMOutgoingMessageTTLCategory", |
| ttl_category, |
| TTL_CATEGORY_COUNT); |
| } |
| |
| } // namespace |
| |
| GCMInternalsBuilder::GCMInternalsBuilder() {} |
| GCMInternalsBuilder::~GCMInternalsBuilder() {} |
| |
| scoped_ptr<base::Clock> GCMInternalsBuilder::BuildClock() { |
| return make_scoped_ptr<base::Clock>(new base::DefaultClock()); |
| } |
| |
| scoped_ptr<MCSClient> GCMInternalsBuilder::BuildMCSClient( |
| const std::string& version, |
| base::Clock* clock, |
| ConnectionFactory* connection_factory, |
| GCMStore* gcm_store, |
| GCMStatsRecorder* recorder) { |
| return make_scoped_ptr<MCSClient>( |
| new MCSClient(version, |
| clock, |
| connection_factory, |
| gcm_store, |
| recorder)); |
| } |
| |
| scoped_ptr<ConnectionFactory> GCMInternalsBuilder::BuildConnectionFactory( |
| const std::vector<GURL>& endpoints, |
| const net::BackoffEntry::Policy& backoff_policy, |
| scoped_refptr<net::HttpNetworkSession> network_session, |
| net::NetLog* net_log, |
| GCMStatsRecorder* recorder) { |
| return make_scoped_ptr<ConnectionFactory>( |
| new ConnectionFactoryImpl(endpoints, |
| backoff_policy, |
| network_session, |
| net_log, |
| recorder)); |
| } |
| |
| GCMClientImpl::GCMClientImpl(scoped_ptr<GCMInternalsBuilder> internals_builder) |
| : internals_builder_(internals_builder.Pass()), |
| state_(UNINITIALIZED), |
| delegate_(NULL), |
| clock_(internals_builder_->BuildClock()), |
| url_request_context_getter_(NULL), |
| pending_registration_requests_deleter_(&pending_registration_requests_), |
| pending_unregistration_requests_deleter_( |
| &pending_unregistration_requests_), |
| periodic_checkin_ptr_factory_(this), |
| weak_ptr_factory_(this) { |
| } |
| |
| GCMClientImpl::~GCMClientImpl() { |
| } |
| |
| void GCMClientImpl::Initialize( |
| const ChromeBuildInfo& chrome_build_info, |
| const base::FilePath& path, |
| const scoped_refptr<base::SequencedTaskRunner>& blocking_task_runner, |
| const scoped_refptr<net::URLRequestContextGetter>& |
| url_request_context_getter, |
| scoped_ptr<Encryptor> encryptor, |
| GCMClient::Delegate* delegate) { |
| DCHECK_EQ(UNINITIALIZED, state_); |
| DCHECK(url_request_context_getter); |
| DCHECK(delegate); |
| |
| url_request_context_getter_ = url_request_context_getter; |
| const net::HttpNetworkSession::Params* network_session_params = |
| url_request_context_getter_->GetURLRequestContext()-> |
| GetNetworkSessionParams(); |
| DCHECK(network_session_params); |
| network_session_ = new net::HttpNetworkSession(*network_session_params); |
| |
| chrome_build_info_ = chrome_build_info; |
| |
| gcm_store_.reset( |
| new GCMStoreImpl(path, blocking_task_runner, encryptor.Pass())); |
| |
| delegate_ = delegate; |
| |
| recorder_.SetDelegate(this); |
| |
| state_ = INITIALIZED; |
| } |
| |
| void GCMClientImpl::Start() { |
| DCHECK_EQ(INITIALIZED, state_); |
| |
| // Once the loading is completed, the check-in will be initiated. |
| gcm_store_->Load(base::Bind(&GCMClientImpl::OnLoadCompleted, |
| weak_ptr_factory_.GetWeakPtr())); |
| state_ = LOADING; |
| } |
| |
| void GCMClientImpl::OnLoadCompleted(scoped_ptr<GCMStore::LoadResult> result) { |
| DCHECK_EQ(LOADING, state_); |
| |
| if (!result->success) { |
| ResetState(); |
| return; |
| } |
| |
| registrations_ = result->registrations; |
| device_checkin_info_.android_id = result->device_android_id; |
| device_checkin_info_.secret = result->device_security_token; |
| last_checkin_time_ = result->last_checkin_time; |
| gservices_settings_.UpdateFromLoadResult(*result); |
| InitializeMCSClient(result.Pass()); |
| |
| if (device_checkin_info_.IsValid()) { |
| SchedulePeriodicCheckin(); |
| OnReady(); |
| return; |
| } |
| |
| state_ = INITIAL_DEVICE_CHECKIN; |
| device_checkin_info_.Reset(); |
| StartCheckin(); |
| } |
| |
| void GCMClientImpl::InitializeMCSClient( |
| scoped_ptr<GCMStore::LoadResult> result) { |
| std::vector<GURL> endpoints; |
| endpoints.push_back(gservices_settings_.GetMCSMainEndpoint()); |
| endpoints.push_back(gservices_settings_.GetMCSFallbackEndpoint()); |
| connection_factory_ = internals_builder_->BuildConnectionFactory( |
| endpoints, |
| kDefaultBackoffPolicy, |
| network_session_, |
| net_log_.net_log(), |
| &recorder_); |
| connection_factory_->SetConnectionListener(this); |
| mcs_client_ = internals_builder_->BuildMCSClient( |
| chrome_build_info_.version, |
| clock_.get(), |
| connection_factory_.get(), |
| gcm_store_.get(), |
| &recorder_).Pass(); |
| |
| mcs_client_->Initialize( |
| base::Bind(&GCMClientImpl::OnMCSError, weak_ptr_factory_.GetWeakPtr()), |
| base::Bind(&GCMClientImpl::OnMessageReceivedFromMCS, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::Bind(&GCMClientImpl::OnMessageSentToMCS, |
| weak_ptr_factory_.GetWeakPtr()), |
| result.Pass()); |
| } |
| |
| void GCMClientImpl::OnFirstTimeDeviceCheckinCompleted( |
| const CheckinInfo& checkin_info) { |
| DCHECK(!device_checkin_info_.IsValid()); |
| |
| device_checkin_info_.android_id = checkin_info.android_id; |
| device_checkin_info_.secret = checkin_info.secret; |
| gcm_store_->SetDeviceCredentials( |
| checkin_info.android_id, checkin_info.secret, |
| base::Bind(&GCMClientImpl::SetDeviceCredentialsCallback, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| OnReady(); |
| } |
| |
| void GCMClientImpl::OnReady() { |
| state_ = READY; |
| StartMCSLogin(); |
| |
| delegate_->OnGCMReady(); |
| } |
| |
| void GCMClientImpl::StartMCSLogin() { |
| DCHECK_EQ(READY, state_); |
| DCHECK(device_checkin_info_.IsValid()); |
| mcs_client_->Login(device_checkin_info_.android_id, |
| device_checkin_info_.secret); |
| } |
| |
| void GCMClientImpl::ResetState() { |
| state_ = UNINITIALIZED; |
| // TODO(fgorski): reset all of the necessart objects and start over. |
| } |
| |
| void GCMClientImpl::StartCheckin() { |
| // Make sure no checkin is in progress. |
| if (checkin_request_.get()) |
| return; |
| |
| checkin_proto::ChromeBuildProto chrome_build_proto; |
| ToCheckinProtoVersion(chrome_build_info_, &chrome_build_proto); |
| CheckinRequest::RequestInfo request_info(device_checkin_info_.android_id, |
| device_checkin_info_.secret, |
| gservices_settings_.digest(), |
| chrome_build_proto); |
| checkin_request_.reset( |
| new CheckinRequest(gservices_settings_.GetCheckinURL(), |
| request_info, |
| kDefaultBackoffPolicy, |
| base::Bind(&GCMClientImpl::OnCheckinCompleted, |
| weak_ptr_factory_.GetWeakPtr()), |
| url_request_context_getter_, |
| &recorder_)); |
| checkin_request_->Start(); |
| } |
| |
| void GCMClientImpl::OnCheckinCompleted( |
| const checkin_proto::AndroidCheckinResponse& checkin_response) { |
| checkin_request_.reset(); |
| |
| if (!checkin_response.has_android_id() || |
| !checkin_response.has_security_token()) { |
| // TODO(fgorski): I don't think a retry here will help, we should probably |
| // start over. By checking in with (0, 0). |
| return; |
| } |
| |
| CheckinInfo checkin_info; |
| checkin_info.android_id = checkin_response.android_id(); |
| checkin_info.secret = checkin_response.security_token(); |
| |
| if (state_ == INITIAL_DEVICE_CHECKIN) { |
| OnFirstTimeDeviceCheckinCompleted(checkin_info); |
| } else { |
| // checkin_info is not expected to change after a periodic checkin as it |
| // would invalidate the registratoin IDs. |
| DCHECK_EQ(READY, state_); |
| DCHECK_EQ(device_checkin_info_.android_id, checkin_info.android_id); |
| DCHECK_EQ(device_checkin_info_.secret, checkin_info.secret); |
| } |
| |
| if (device_checkin_info_.IsValid()) { |
| // First update G-services settings, as something might have changed. |
| if (gservices_settings_.UpdateFromCheckinResponse(checkin_response)) { |
| gcm_store_->SetGServicesSettings( |
| gservices_settings_.settings_map(), |
| gservices_settings_.digest(), |
| base::Bind(&GCMClientImpl::SetGServicesSettingsCallback, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| last_checkin_time_ = clock_->Now(); |
| gcm_store_->SetLastCheckinTime( |
| last_checkin_time_, |
| base::Bind(&GCMClientImpl::SetLastCheckinTimeCallback, |
| weak_ptr_factory_.GetWeakPtr())); |
| SchedulePeriodicCheckin(); |
| } |
| } |
| |
| void GCMClientImpl::SetGServicesSettingsCallback(bool success) { |
| DCHECK(success); |
| } |
| |
| void GCMClientImpl::SchedulePeriodicCheckin() { |
| // Make sure no checkin is in progress. |
| if (checkin_request_.get()) |
| return; |
| |
| // There should be only one periodic checkin pending at a time. Removing |
| // pending periodic checkin to schedule a new one. |
| periodic_checkin_ptr_factory_.InvalidateWeakPtrs(); |
| |
| base::TimeDelta time_to_next_checkin = GetTimeToNextCheckin(); |
| if (time_to_next_checkin < base::TimeDelta()) |
| time_to_next_checkin = base::TimeDelta(); |
| |
| base::MessageLoop::current()->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&GCMClientImpl::StartCheckin, |
| periodic_checkin_ptr_factory_.GetWeakPtr()), |
| time_to_next_checkin); |
| } |
| |
| base::TimeDelta GCMClientImpl::GetTimeToNextCheckin() const { |
| return last_checkin_time_ + gservices_settings_.GetCheckinInterval() - |
| clock_->Now(); |
| } |
| |
| void GCMClientImpl::SetLastCheckinTimeCallback(bool success) { |
| // TODO(fgorski): This is one of the signals that store needs a rebuild. |
| DCHECK(success); |
| } |
| |
| void GCMClientImpl::SetDeviceCredentialsCallback(bool success) { |
| // TODO(fgorski): This is one of the signals that store needs a rebuild. |
| DCHECK(success); |
| } |
| |
| void GCMClientImpl::UpdateRegistrationCallback(bool success) { |
| // TODO(fgorski): This is one of the signals that store needs a rebuild. |
| DCHECK(success); |
| } |
| |
| void GCMClientImpl::Stop() { |
| weak_ptr_factory_.InvalidateWeakPtrs(); |
| device_checkin_info_.Reset(); |
| connection_factory_.reset(); |
| delegate_->OnDisconnected(); |
| mcs_client_.reset(); |
| checkin_request_.reset(); |
| pending_registration_requests_.clear(); |
| state_ = INITIALIZED; |
| gcm_store_->Close(); |
| } |
| |
| void GCMClientImpl::CheckOut() { |
| Stop(); |
| gcm_store_->Destroy(base::Bind(&GCMClientImpl::OnGCMStoreDestroyed, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void GCMClientImpl::Register(const std::string& app_id, |
| const std::vector<std::string>& sender_ids) { |
| DCHECK_EQ(state_, READY); |
| |
| // If the same sender ids is provided, return the cached registration ID |
| // directly. |
| RegistrationInfoMap::const_iterator registrations_iter = |
| registrations_.find(app_id); |
| if (registrations_iter != registrations_.end() && |
| registrations_iter->second->sender_ids == sender_ids) { |
| delegate_->OnRegisterFinished( |
| app_id, registrations_iter->second->registration_id, SUCCESS); |
| return; |
| } |
| |
| RegistrationRequest::RequestInfo request_info( |
| device_checkin_info_.android_id, |
| device_checkin_info_.secret, |
| app_id, |
| sender_ids); |
| DCHECK_EQ(0u, pending_registration_requests_.count(app_id)); |
| |
| RegistrationRequest* registration_request = |
| new RegistrationRequest(gservices_settings_.GetRegistrationURL(), |
| request_info, |
| kDefaultBackoffPolicy, |
| base::Bind(&GCMClientImpl::OnRegisterCompleted, |
| weak_ptr_factory_.GetWeakPtr(), |
| app_id, |
| sender_ids), |
| kMaxRegistrationRetries, |
| url_request_context_getter_, |
| &recorder_); |
| pending_registration_requests_[app_id] = registration_request; |
| registration_request->Start(); |
| } |
| |
| void GCMClientImpl::OnRegisterCompleted( |
| const std::string& app_id, |
| const std::vector<std::string>& sender_ids, |
| RegistrationRequest::Status status, |
| const std::string& registration_id) { |
| DCHECK(delegate_); |
| |
| Result result; |
| PendingRegistrationRequests::iterator iter = |
| pending_registration_requests_.find(app_id); |
| if (iter == pending_registration_requests_.end()) |
| result = UNKNOWN_ERROR; |
| else if (status == RegistrationRequest::INVALID_SENDER) |
| result = INVALID_PARAMETER; |
| else if (registration_id.empty()) |
| result = SERVER_ERROR; |
| else |
| result = SUCCESS; |
| |
| if (result == SUCCESS) { |
| // Cache it. |
| linked_ptr<RegistrationInfo> registration(new RegistrationInfo); |
| registration->sender_ids = sender_ids; |
| registration->registration_id = registration_id; |
| registrations_[app_id] = registration; |
| |
| // Save it in the persistent store. |
| gcm_store_->AddRegistration( |
| app_id, |
| registration, |
| base::Bind(&GCMClientImpl::UpdateRegistrationCallback, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| delegate_->OnRegisterFinished( |
| app_id, result == SUCCESS ? registration_id : std::string(), result); |
| |
| if (iter != pending_registration_requests_.end()) { |
| delete iter->second; |
| pending_registration_requests_.erase(iter); |
| } |
| } |
| |
| void GCMClientImpl::Unregister(const std::string& app_id) { |
| DCHECK_EQ(state_, READY); |
| if (pending_unregistration_requests_.count(app_id) == 1) |
| return; |
| |
| // Remove from the cache and persistent store. |
| registrations_.erase(app_id); |
| gcm_store_->RemoveRegistration( |
| app_id, |
| base::Bind(&GCMClientImpl::UpdateRegistrationCallback, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| UnregistrationRequest::RequestInfo request_info( |
| device_checkin_info_.android_id, |
| device_checkin_info_.secret, |
| app_id); |
| |
| UnregistrationRequest* unregistration_request = new UnregistrationRequest( |
| gservices_settings_.GetRegistrationURL(), |
| request_info, |
| kDefaultBackoffPolicy, |
| base::Bind(&GCMClientImpl::OnUnregisterCompleted, |
| weak_ptr_factory_.GetWeakPtr(), |
| app_id), |
| url_request_context_getter_, |
| &recorder_); |
| pending_unregistration_requests_[app_id] = unregistration_request; |
| unregistration_request->Start(); |
| } |
| |
| void GCMClientImpl::OnUnregisterCompleted( |
| const std::string& app_id, |
| UnregistrationRequest::Status status) { |
| DVLOG(1) << "Unregister completed for app: " << app_id |
| << " with " << (status ? "success." : "failure."); |
| delegate_->OnUnregisterFinished( |
| app_id, |
| status == UnregistrationRequest::SUCCESS ? SUCCESS : SERVER_ERROR); |
| |
| PendingUnregistrationRequests::iterator iter = |
| pending_unregistration_requests_.find(app_id); |
| if (iter == pending_unregistration_requests_.end()) |
| return; |
| |
| delete iter->second; |
| pending_unregistration_requests_.erase(iter); |
| } |
| |
| void GCMClientImpl::OnGCMStoreDestroyed(bool success) { |
| DLOG_IF(ERROR, !success) << "GCM store failed to be destroyed!"; |
| UMA_HISTOGRAM_BOOLEAN("GCM.StoreDestroySucceeded", success); |
| } |
| |
| void GCMClientImpl::Send(const std::string& app_id, |
| const std::string& receiver_id, |
| const OutgoingMessage& message) { |
| DCHECK_EQ(state_, READY); |
| |
| RecordOutgoingMessageToUMA(message); |
| |
| mcs_proto::DataMessageStanza stanza; |
| stanza.set_ttl(message.time_to_live); |
| stanza.set_sent(clock_->Now().ToInternalValue() / |
| base::Time::kMicrosecondsPerSecond); |
| stanza.set_id(message.id); |
| stanza.set_from(kSendMessageFromValue); |
| stanza.set_to(receiver_id); |
| stanza.set_category(app_id); |
| |
| for (MessageData::const_iterator iter = message.data.begin(); |
| iter != message.data.end(); |
| ++iter) { |
| mcs_proto::AppData* app_data = stanza.add_app_data(); |
| app_data->set_key(iter->first); |
| app_data->set_value(iter->second); |
| } |
| |
| MCSMessage mcs_message(stanza); |
| DVLOG(1) << "MCS message size: " << mcs_message.size(); |
| mcs_client_->SendMessage(mcs_message); |
| } |
| |
| std::string GCMClientImpl::GetStateString() const { |
| switch(state_) { |
| case GCMClientImpl::INITIALIZED: |
| return "INITIALIZED"; |
| case GCMClientImpl::UNINITIALIZED: |
| return "UNINITIALIZED"; |
| case GCMClientImpl::LOADING: |
| return "LOADING"; |
| case GCMClientImpl::INITIAL_DEVICE_CHECKIN: |
| return "INITIAL_DEVICE_CHECKIN"; |
| case GCMClientImpl::READY: |
| return "READY"; |
| default: |
| NOTREACHED(); |
| return std::string(); |
| } |
| } |
| |
| void GCMClientImpl::SetRecording(bool recording) { |
| recorder_.SetRecording(recording); |
| } |
| |
| void GCMClientImpl::ClearActivityLogs() { |
| recorder_.Clear(); |
| } |
| |
| GCMClient::GCMStatistics GCMClientImpl::GetStatistics() const { |
| GCMClient::GCMStatistics stats; |
| stats.gcm_client_created = true; |
| stats.is_recording = recorder_.is_recording(); |
| stats.gcm_client_state = GetStateString(); |
| stats.connection_client_created = mcs_client_.get() != NULL; |
| if (connection_factory_.get()) |
| stats.connection_state = connection_factory_->GetConnectionStateString(); |
| if (mcs_client_.get()) { |
| stats.send_queue_size = mcs_client_->GetSendQueueSize(); |
| stats.resend_queue_size = mcs_client_->GetResendQueueSize(); |
| } |
| if (device_checkin_info_.android_id > 0) |
| stats.android_id = device_checkin_info_.android_id; |
| recorder_.CollectActivities(&stats.recorded_activities); |
| |
| for (RegistrationInfoMap::const_iterator it = registrations_.begin(); |
| it != registrations_.end(); ++it) { |
| stats.registered_app_ids.push_back(it->first); |
| } |
| return stats; |
| } |
| |
| void GCMClientImpl::OnActivityRecorded() { |
| delegate_->OnActivityRecorded(); |
| } |
| |
| void GCMClientImpl::OnConnected(const GURL& current_server, |
| const net::IPEndPoint& ip_endpoint) { |
| // TODO(gcm): expose current server in debug page. |
| delegate_->OnActivityRecorded(); |
| delegate_->OnConnected(ip_endpoint); |
| } |
| |
| void GCMClientImpl::OnDisconnected() { |
| delegate_->OnActivityRecorded(); |
| delegate_->OnDisconnected(); |
| } |
| |
| void GCMClientImpl::OnMessageReceivedFromMCS(const gcm::MCSMessage& message) { |
| switch (message.tag()) { |
| case kLoginResponseTag: |
| DVLOG(1) << "Login response received by GCM Client. Ignoring."; |
| return; |
| case kDataMessageStanzaTag: |
| DVLOG(1) << "A downstream message received. Processing..."; |
| HandleIncomingMessage(message); |
| return; |
| default: |
| NOTREACHED() << "Message with unexpected tag received by GCMClient"; |
| return; |
| } |
| } |
| |
| void GCMClientImpl::OnMessageSentToMCS(int64 user_serial_number, |
| const std::string& app_id, |
| const std::string& message_id, |
| MCSClient::MessageSendStatus status) { |
| DCHECK_EQ(user_serial_number, kDefaultUserSerialNumber); |
| DCHECK(delegate_); |
| |
| // TTL_EXCEEDED is singled out here, because it can happen long time after the |
| // message was sent. That is why it comes as |OnMessageSendError| event rather |
| // than |OnSendFinished|. SendErrorDetails.additional_data is left empty. |
| // All other errors will be raised immediately, through asynchronous callback. |
| // It is expected that TTL_EXCEEDED will be issued for a message that was |
| // previously issued |OnSendFinished| with status SUCCESS. |
| // For now, we do not report that the message has been sent and acked |
| // successfully. |
| // TODO(jianli): Consider adding UMA for this status. |
| if (status == MCSClient::TTL_EXCEEDED) { |
| SendErrorDetails send_error_details; |
| send_error_details.message_id = message_id; |
| send_error_details.result = GCMClient::TTL_EXCEEDED; |
| delegate_->OnMessageSendError(app_id, send_error_details); |
| } else if (status != MCSClient::SENT) { |
| delegate_->OnSendFinished(app_id, message_id, ToGCMClientResult(status)); |
| } |
| } |
| |
| void GCMClientImpl::OnMCSError() { |
| // TODO(fgorski): For now it replaces the initialization method. Long term it |
| // should have an error or status passed in. |
| } |
| |
| void GCMClientImpl::HandleIncomingMessage(const gcm::MCSMessage& message) { |
| DCHECK(delegate_); |
| |
| const mcs_proto::DataMessageStanza& data_message_stanza = |
| reinterpret_cast<const mcs_proto::DataMessageStanza&>( |
| message.GetProtobuf()); |
| DCHECK_EQ(data_message_stanza.device_user_id(), kDefaultUserSerialNumber); |
| |
| // Copying all the data from the stanza to a MessageData object. When present, |
| // keys like kMessageTypeKey or kSendErrorMessageIdKey will be filtered out |
| // later. |
| MessageData message_data; |
| for (int i = 0; i < data_message_stanza.app_data_size(); ++i) { |
| std::string key = data_message_stanza.app_data(i).key(); |
| message_data[key] = data_message_stanza.app_data(i).value(); |
| } |
| |
| MessageType message_type = DATA_MESSAGE; |
| MessageData::iterator iter = message_data.find(kMessageTypeKey); |
| if (iter != message_data.end()) { |
| message_type = DecodeMessageType(iter->second); |
| message_data.erase(iter); |
| } |
| |
| switch (message_type) { |
| case DATA_MESSAGE: |
| HandleIncomingDataMessage(data_message_stanza, message_data); |
| break; |
| case DELETED_MESSAGES: |
| recorder_.RecordDataMessageReceived(data_message_stanza.category(), |
| data_message_stanza.from(), |
| data_message_stanza.ByteSize(), |
| true, |
| GCMStatsRecorder::DELETED_MESSAGES); |
| delegate_->OnMessagesDeleted(data_message_stanza.category()); |
| break; |
| case SEND_ERROR: |
| HandleIncomingSendError(data_message_stanza, message_data); |
| break; |
| case UNKNOWN: |
| default: // Treat default the same as UNKNOWN. |
| DVLOG(1) << "Unknown message_type received. Message ignored. " |
| << "App ID: " << data_message_stanza.category() << "."; |
| break; |
| } |
| } |
| |
| void GCMClientImpl::HandleIncomingDataMessage( |
| const mcs_proto::DataMessageStanza& data_message_stanza, |
| MessageData& message_data) { |
| std::string app_id = data_message_stanza.category(); |
| |
| // Drop the message when the app is not registered for the sender of the |
| // message. |
| RegistrationInfoMap::iterator iter = registrations_.find(app_id); |
| bool not_registered = |
| iter == registrations_.end() || |
| std::find(iter->second->sender_ids.begin(), |
| iter->second->sender_ids.end(), |
| data_message_stanza.from()) == iter->second->sender_ids.end(); |
| recorder_.RecordDataMessageReceived(app_id, data_message_stanza.from(), |
| data_message_stanza.ByteSize(), !not_registered, |
| GCMStatsRecorder::DATA_MESSAGE); |
| if (not_registered) { |
| return; |
| } |
| |
| IncomingMessage incoming_message; |
| incoming_message.sender_id = data_message_stanza.from(); |
| if (data_message_stanza.has_token()) |
| incoming_message.collapse_key = data_message_stanza.token(); |
| incoming_message.data = message_data; |
| delegate_->OnMessageReceived(app_id, incoming_message); |
| } |
| |
| void GCMClientImpl::HandleIncomingSendError( |
| const mcs_proto::DataMessageStanza& data_message_stanza, |
| MessageData& message_data) { |
| SendErrorDetails send_error_details; |
| send_error_details.additional_data = message_data; |
| send_error_details.result = SERVER_ERROR; |
| |
| MessageData::iterator iter = |
| send_error_details.additional_data.find(kSendErrorMessageIdKey); |
| if (iter != send_error_details.additional_data.end()) { |
| send_error_details.message_id = iter->second; |
| send_error_details.additional_data.erase(iter); |
| } |
| |
| recorder_.RecordIncomingSendError( |
| data_message_stanza.category(), |
| data_message_stanza.to(), |
| data_message_stanza.id()); |
| delegate_->OnMessageSendError(data_message_stanza.category(), |
| send_error_details); |
| } |
| |
| } // namespace gcm |