| // 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 <stddef.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/files/file_path.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/sequenced_task_runner.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/default_clock.h" |
| #include "base/timer/timer.h" |
| #include "components/crx_file/id_util.h" |
| #include "components/gcm_driver/crypto/gcm_decryption_result.h" |
| #include "components/gcm_driver/features.h" |
| #include "components/gcm_driver/gcm_account_mapper.h" |
| #include "components/gcm_driver/gcm_backoff_policy.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_registration_request_handler.h" |
| #include "google_apis/gcm/engine/gcm_store_impl.h" |
| #include "google_apis/gcm/engine/gcm_unregistration_request_handler.h" |
| #include "google_apis/gcm/engine/instance_id_delete_token_request_handler.h" |
| #include "google_apis/gcm/engine/instance_id_get_token_request_handler.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 "services/network/public/cpp/shared_url_loader_factory.h" |
| #include "url/gurl.h" |
| |
| namespace gcm { |
| |
| // It is okay to append to the enum if these states grow. DO NOT reorder, |
| // renumber or otherwise reuse existing values. |
| // Do not assign an explicit value to REGISTRATION_CACHE_STATUS_COUNT, as |
| // this lets the compiler keep it up to date. |
| enum class RegistrationCacheStatus { |
| REGISTRATION_NOT_FOUND = 0, |
| REGISTRATION_FOUND_AND_FRESH = 1, |
| REGISTRATION_FOUND_BUT_STALE = 2, |
| REGISTRATION_FOUND_BUT_SENDERS_DONT_MATCH = 3, |
| // NOTE: always keep this entry at the end. Add new value only immediately |
| // above this line. Make sure to update the corresponding histogram enum |
| // accordingly. |
| REGISTRATION_CACHE_STATUS_COUNT |
| }; |
| |
| namespace { |
| |
| // 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 |
| }; |
| |
| enum ResetStoreError { |
| DESTROYING_STORE_FAILED, |
| INFINITE_STORE_RESET, |
| // NOTE: always keep this entry at the end. Add new value only immediately |
| // above this line. Make sure to update the corresponding histogram enum |
| // accordingly. |
| RESET_STORE_ERROR_COUNT |
| }; |
| |
| const int kMaxRegistrationRetries = 5; |
| const int kMaxUnregistrationRetries = 5; |
| const char kDeletedCountKey[] = "total_deleted"; |
| 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 kSubtypeKey[] = "subtype"; |
| const char kSendMessageFromValue[] = "gcm@chrome.com"; |
| const int64_t kDefaultUserSerialNumber = 0LL; |
| const int kDestroyGCMStoreDelayMS = 5 * 60 * 1000; // 5 minutes. |
| |
| GCMClient::Result ToGCMClientResult(MCSClient::MessageSendStatus status) { |
| switch (status) { |
| case MCSClient::QUEUED: |
| return GCMClient::SUCCESS; |
| case MCSClient::MESSAGE_TOO_LARGE: |
| return GCMClient::INVALID_PARAMETER; |
| case MCSClient::QUEUE_SIZE_LIMIT_REACHED: |
| case MCSClient::APP_QUEUE_SIZE_LIMIT_REACHED: |
| case MCSClient::NO_CONNECTION_ON_ZERO_TTL: |
| case MCSClient::TTL_EXCEEDED: |
| return GCMClient::NETWORK_ERROR; |
| case MCSClient::SENT: |
| case MCSClient::SEND_STATUS_COUNT: |
| 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 = |
| checkin_proto::ChromeBuildProto_Platform_PLATFORM_LINUX; |
| 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_UNSPECIFIED: |
| // For unknown platform, return as LINUX. |
| platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_LINUX; |
| break; |
| } |
| android_build_info->set_platform(platform); |
| |
| checkin_proto::ChromeBuildProto_Channel channel = |
| checkin_proto::ChromeBuildProto_Channel_CHANNEL_UNKNOWN; |
| 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; |
| } |
| 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; |
| } |
| |
| int ConstructGCMVersion(const std::string& chrome_version) { |
| // Major Chrome version is passed as GCM version. |
| size_t pos = chrome_version.find('.'); |
| if (pos == std::string::npos) { |
| NOTREACHED(); |
| return 0; |
| } |
| |
| int gcm_version = 0; |
| base::StringToInt( |
| base::StringPiece(chrome_version.c_str(), pos), &gcm_version); |
| return gcm_version; |
| } |
| |
| std::string SerializeInstanceIDData(const std::string& instance_id, |
| const std::string& extra_data) { |
| DCHECK(!instance_id.empty() && !extra_data.empty()); |
| DCHECK(instance_id.find(',') == std::string::npos); |
| return instance_id + "," + extra_data; |
| } |
| |
| bool DeserializeInstanceIDData(const std::string& serialized_data, |
| std::string* instance_id, |
| std::string* extra_data) { |
| DCHECK(instance_id && extra_data); |
| std::size_t pos = serialized_data.find(','); |
| if (pos == std::string::npos) |
| return false; |
| *instance_id = serialized_data.substr(0, pos); |
| *extra_data = serialized_data.substr(pos + 1); |
| return !instance_id->empty() && !extra_data->empty(); |
| } |
| |
| bool InstanceIDUsesSubtypeForAppId(const std::string& app_id) { |
| // Always use subtypes with Instance ID, except for Chrome Apps/Extensions. |
| return !crx_file::id_util::IdIsValid(app_id); |
| } |
| |
| void RecordOutgoingMessageToUMA(const gcm::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 |
| ttl_category = TTL_MAXIMUM; |
| |
| UMA_HISTOGRAM_ENUMERATION("GCM.OutgoingMessageTTL", |
| ttl_category, |
| TTL_CATEGORY_COUNT); |
| } |
| |
| void RecordResetStoreErrorToUMA(ResetStoreError error) { |
| UMA_HISTOGRAM_ENUMERATION("GCM.ResetStore", error, RESET_STORE_ERROR_COUNT); |
| } |
| |
| } // namespace |
| |
| void RecordRegistrationRequestToUMA(gcm::RegistrationCacheStatus status) { |
| UMA_HISTOGRAM_ENUMERATION( |
| "GCM.RegistrationCacheStatus", status, |
| RegistrationCacheStatus::REGISTRATION_CACHE_STATUS_COUNT); |
| } |
| GCMInternalsBuilder::GCMInternalsBuilder() {} |
| GCMInternalsBuilder::~GCMInternalsBuilder() {} |
| |
| base::Clock* GCMInternalsBuilder::GetClock() { |
| return base::DefaultClock::GetInstance(); |
| } |
| |
| std::unique_ptr<MCSClient> GCMInternalsBuilder::BuildMCSClient( |
| const std::string& version, |
| base::Clock* clock, |
| ConnectionFactory* connection_factory, |
| GCMStore* gcm_store, |
| GCMStatsRecorder* recorder) { |
| return std::unique_ptr<MCSClient>( |
| new MCSClient(version, clock, connection_factory, gcm_store, recorder)); |
| } |
| |
| std::unique_ptr<ConnectionFactory> GCMInternalsBuilder::BuildConnectionFactory( |
| const std::vector<GURL>& endpoints, |
| const net::BackoffEntry::Policy& backoff_policy, |
| base::RepeatingCallback< |
| void(network::mojom::ProxyResolvingSocketFactoryRequest)> |
| get_socket_factory_callback, |
| GCMStatsRecorder* recorder, |
| network::NetworkConnectionTracker* network_connection_tracker) { |
| return std::make_unique<ConnectionFactoryImpl>( |
| endpoints, backoff_policy, std::move(get_socket_factory_callback), |
| recorder, network_connection_tracker); |
| } |
| |
| GCMClientImpl::CheckinInfo::CheckinInfo() |
| : android_id(0), secret(0), accounts_set(false) { |
| } |
| |
| GCMClientImpl::CheckinInfo::~CheckinInfo() { |
| } |
| |
| void GCMClientImpl::CheckinInfo::SnapshotCheckinAccounts() { |
| last_checkin_accounts.clear(); |
| for (auto iter = account_tokens.begin(); iter != account_tokens.end(); |
| ++iter) { |
| last_checkin_accounts.insert(iter->first); |
| } |
| } |
| |
| void GCMClientImpl::CheckinInfo::Reset() { |
| android_id = 0; |
| secret = 0; |
| accounts_set = false; |
| account_tokens.clear(); |
| last_checkin_accounts.clear(); |
| } |
| |
| GCMClientImpl::GCMClientImpl( |
| std::unique_ptr<GCMInternalsBuilder> internals_builder) |
| : internals_builder_(std::move(internals_builder)), |
| state_(UNINITIALIZED), |
| delegate_(nullptr), |
| start_mode_(DELAYED_START), |
| clock_(internals_builder_->GetClock()), |
| gcm_store_reset_(false), |
| network_connection_tracker_(nullptr), |
| periodic_checkin_ptr_factory_(this), |
| destroying_gcm_store_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, |
| base::RepeatingCallback< |
| void(network::mojom::ProxyResolvingSocketFactoryRequest)> |
| get_socket_factory_callback, |
| const scoped_refptr<network::SharedURLLoaderFactory>& url_loader_factory, |
| network::NetworkConnectionTracker* network_connection_tracker, |
| std::unique_ptr<Encryptor> encryptor, |
| GCMClient::Delegate* delegate) { |
| DCHECK_EQ(UNINITIALIZED, state_); |
| DCHECK(delegate); |
| |
| get_socket_factory_callback_ = std::move(get_socket_factory_callback); |
| url_loader_factory_ = url_loader_factory; |
| network_connection_tracker_ = network_connection_tracker; |
| chrome_build_info_ = chrome_build_info; |
| |
| gcm_store_.reset( |
| new GCMStoreImpl(path, blocking_task_runner, std::move(encryptor))); |
| |
| delegate_ = delegate; |
| |
| recorder_.SetDelegate(this); |
| |
| state_ = INITIALIZED; |
| } |
| |
| void GCMClientImpl::Start(StartMode start_mode) { |
| DCHECK_NE(UNINITIALIZED, state_); |
| |
| if (state_ == LOADED) { |
| // Start the GCM if not yet. |
| if (start_mode == IMMEDIATE_START) { |
| // Give up the scheduling to wipe out the store since now some one starts |
| // to use GCM. |
| destroying_gcm_store_ptr_factory_.InvalidateWeakPtrs(); |
| |
| StartGCM(); |
| } |
| return; |
| } |
| |
| // The delay start behavior will be abandoned when Start has been called |
| // once with IMMEDIATE_START behavior. |
| if (start_mode == IMMEDIATE_START) |
| start_mode_ = IMMEDIATE_START; |
| |
| // Bail out if the loading is not started or completed. |
| if (state_ != INITIALIZED) |
| return; |
| |
| // Once the loading is completed, the check-in will be initiated. |
| // If we're in lazy start mode, don't create a new store since none is really |
| // using GCM functionality yet. |
| gcm_store_->Load( |
| (start_mode == IMMEDIATE_START) ? |
| GCMStore::CREATE_IF_MISSING : |
| GCMStore::DO_NOT_CREATE, |
| base::Bind(&GCMClientImpl::OnLoadCompleted, |
| weak_ptr_factory_.GetWeakPtr())); |
| state_ = LOADING; |
| } |
| |
| void GCMClientImpl::OnLoadCompleted( |
| std::unique_ptr<GCMStore::LoadResult> result) { |
| DCHECK_EQ(LOADING, state_); |
| |
| if (!result->success) { |
| if (result->store_does_not_exist) { |
| if (start_mode_ == IMMEDIATE_START) { |
| // An immediate start was requested during the delayed start that just |
| // completed. Perform it now. |
| gcm_store_->Load(GCMStore::CREATE_IF_MISSING, |
| base::Bind(&GCMClientImpl::OnLoadCompleted, |
| weak_ptr_factory_.GetWeakPtr())); |
| } else { |
| // In the case that the store does not exist, set |state_| back to |
| // INITIALIZED such that store loading could be triggered again when |
| // Start() is called with IMMEDIATE_START. |
| state_ = INITIALIZED; |
| } |
| } else { |
| // Otherwise, destroy the store to try again. |
| ResetStore(); |
| } |
| return; |
| } |
| gcm_store_reset_ = false; |
| |
| device_checkin_info_.android_id = result->device_android_id; |
| device_checkin_info_.secret = result->device_security_token; |
| device_checkin_info_.last_checkin_accounts = result->last_checkin_accounts; |
| // A case where there were previously no accounts reported with checkin is |
| // considered to be the same as when the list of accounts is empty. It enables |
| // scheduling a periodic checkin for devices with no signed in users |
| // immediately after restart, while keeping |accounts_set == false| delays the |
| // checkin until the list of accounts is set explicitly. |
| if (result->last_checkin_accounts.size() == 0) |
| device_checkin_info_.accounts_set = true; |
| last_checkin_time_ = result->last_checkin_time; |
| gservices_settings_.UpdateFromLoadResult(*result); |
| |
| for (auto iter = result->registrations.begin(); |
| iter != result->registrations.end(); |
| ++iter) { |
| std::string registration_id; |
| scoped_refptr<RegistrationInfo> registration = |
| RegistrationInfo::BuildFromString(iter->first, iter->second, |
| ®istration_id); |
| // TODO(jianli): Add UMA to track the error case. |
| if (registration) |
| registrations_.emplace(std::move(registration), registration_id); |
| } |
| |
| for (auto iter = result->instance_id_data.begin(); |
| iter != result->instance_id_data.end(); |
| ++iter) { |
| std::string instance_id; |
| std::string extra_data; |
| if (DeserializeInstanceIDData(iter->second, &instance_id, &extra_data)) |
| instance_id_data_[iter->first] = std::make_pair(instance_id, extra_data); |
| } |
| |
| load_result_ = std::move(result); |
| state_ = LOADED; |
| |
| // Don't initiate the GCM connection when GCM is in delayed start mode and |
| // not any standalone app has registered GCM yet. |
| if (start_mode_ == DELAYED_START && !HasStandaloneRegisteredApp()) { |
| // If no standalone app is using GCM and the device ID is present, schedule |
| // to have the store wiped out. |
| if (device_checkin_info_.android_id) { |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&GCMClientImpl::DestroyStoreWhenNotNeeded, |
| destroying_gcm_store_ptr_factory_.GetWeakPtr()), |
| base::TimeDelta::FromMilliseconds(kDestroyGCMStoreDelayMS)); |
| } |
| |
| return; |
| } |
| |
| StartGCM(); |
| } |
| |
| void GCMClientImpl::StartGCM() { |
| // Taking over the value of account_mappings before passing the ownership of |
| // load result to InitializeMCSClient. |
| std::vector<AccountMapping> account_mappings; |
| account_mappings.swap(load_result_->account_mappings); |
| base::Time last_token_fetch_time = load_result_->last_token_fetch_time; |
| |
| InitializeMCSClient(); |
| |
| if (device_checkin_info_.IsValid()) { |
| SchedulePeriodicCheckin(); |
| OnReady(account_mappings, last_token_fetch_time); |
| return; |
| } |
| |
| state_ = INITIAL_DEVICE_CHECKIN; |
| device_checkin_info_.Reset(); |
| StartCheckin(); |
| } |
| |
| void GCMClientImpl::InitializeMCSClient() { |
| DCHECK(network_connection_tracker_); |
| std::vector<GURL> endpoints; |
| endpoints.push_back(gservices_settings_.GetMCSMainEndpoint()); |
| GURL fallback_endpoint = gservices_settings_.GetMCSFallbackEndpoint(); |
| if (fallback_endpoint.is_valid()) |
| endpoints.push_back(fallback_endpoint); |
| connection_factory_ = internals_builder_->BuildConnectionFactory( |
| endpoints, GetGCMBackoffPolicy(), get_socket_factory_callback_, |
| &recorder_, network_connection_tracker_); |
| connection_factory_->SetConnectionListener(this); |
| mcs_client_ = internals_builder_->BuildMCSClient( |
| chrome_build_info_.version, clock_, connection_factory_.get(), |
| gcm_store_.get(), &recorder_); |
| |
| 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()), |
| std::move(load_result_)); |
| } |
| |
| 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; |
| // If accounts were not set by now, we can consider them set (to empty list) |
| // to make sure periodic checkins get scheduled after initial checkin. |
| device_checkin_info_.accounts_set = true; |
| gcm_store_->SetDeviceCredentials( |
| checkin_info.android_id, checkin_info.secret, |
| base::Bind(&GCMClientImpl::SetDeviceCredentialsCallback, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| OnReady(std::vector<AccountMapping>(), base::Time()); |
| } |
| |
| void GCMClientImpl::OnReady(const std::vector<AccountMapping>& account_mappings, |
| const base::Time& last_token_fetch_time) { |
| state_ = READY; |
| StartMCSLogin(); |
| |
| delegate_->OnGCMReady(account_mappings, last_token_fetch_time); |
| } |
| |
| 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::DestroyStoreWhenNotNeeded() { |
| if (state_ != LOADED || start_mode_ != DELAYED_START) |
| return; |
| |
| gcm_store_->Destroy(base::Bind(&GCMClientImpl::DestroyStoreCallback, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void GCMClientImpl::ResetStore() { |
| // If already being reset, don't do it again. We want to prevent from |
| // resetting and loading from the store again and again. |
| if (gcm_store_reset_) { |
| RecordResetStoreErrorToUMA(INFINITE_STORE_RESET); |
| state_ = UNINITIALIZED; |
| return; |
| } |
| gcm_store_reset_ = true; |
| |
| // Destroy the GCM store to start over. |
| gcm_store_->Destroy(base::Bind(&GCMClientImpl::ResetStoreCallback, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void GCMClientImpl::SetAccountTokens( |
| const std::vector<AccountTokenInfo>& account_tokens) { |
| device_checkin_info_.account_tokens.clear(); |
| for (auto iter = account_tokens.begin(); iter != account_tokens.end(); |
| ++iter) { |
| device_checkin_info_.account_tokens[iter->email] = iter->access_token; |
| } |
| |
| bool accounts_set_before = device_checkin_info_.accounts_set; |
| device_checkin_info_.accounts_set = true; |
| |
| DVLOG(1) << "Set account called with: " << account_tokens.size() |
| << " accounts."; |
| |
| if (state_ != READY && state_ != INITIAL_DEVICE_CHECKIN) |
| return; |
| |
| bool account_removed = false; |
| for (auto iter = device_checkin_info_.last_checkin_accounts.begin(); |
| iter != device_checkin_info_.last_checkin_accounts.end(); ++iter) { |
| if (device_checkin_info_.account_tokens.find(*iter) == |
| device_checkin_info_.account_tokens.end()) { |
| account_removed = true; |
| } |
| } |
| |
| // Checkin will be forced when any of the accounts was removed during the |
| // current Chrome session or if there has been an account removed between the |
| // restarts of Chrome. If there is a checkin in progress, it will be canceled. |
| // We only force checkin when user signs out. When there is a new account |
| // signed in, the periodic checkin will take care of adding the association in |
| // reasonable time. |
| if (account_removed) { |
| DVLOG(1) << "Detected that account has been removed. Forcing checkin."; |
| checkin_request_.reset(); |
| StartCheckin(); |
| } else if (!accounts_set_before) { |
| SchedulePeriodicCheckin(); |
| DVLOG(1) << "Accounts set for the first time. Scheduled periodic checkin."; |
| } |
| } |
| |
| void GCMClientImpl::UpdateAccountMapping( |
| const AccountMapping& account_mapping) { |
| gcm_store_->AddAccountMapping(account_mapping, |
| base::Bind(&GCMClientImpl::DefaultStoreCallback, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void GCMClientImpl::RemoveAccountMapping(const std::string& account_id) { |
| gcm_store_->RemoveAccountMapping( |
| account_id, |
| base::Bind(&GCMClientImpl::DefaultStoreCallback, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void GCMClientImpl::SetLastTokenFetchTime(const base::Time& time) { |
| gcm_store_->SetLastTokenFetchTime( |
| time, |
| base::Bind(&GCMClientImpl::IgnoreWriteResultCallback, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void GCMClientImpl::UpdateHeartbeatTimer( |
| std::unique_ptr<base::RetainingOneShotTimer> timer) { |
| DCHECK(mcs_client_); |
| mcs_client_->UpdateHeartbeatTimer(std::move(timer)); |
| } |
| |
| void GCMClientImpl::AddInstanceIDData(const std::string& app_id, |
| const std::string& instance_id, |
| const std::string& extra_data) { |
| instance_id_data_[app_id] = std::make_pair(instance_id, extra_data); |
| gcm_store_->AddInstanceIDData( |
| app_id, |
| SerializeInstanceIDData(instance_id, extra_data), |
| base::Bind(&GCMClientImpl::IgnoreWriteResultCallback, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void GCMClientImpl::RemoveInstanceIDData(const std::string& app_id) { |
| instance_id_data_.erase(app_id); |
| gcm_store_->RemoveInstanceIDData( |
| app_id, |
| base::Bind(&GCMClientImpl::IgnoreWriteResultCallback, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void GCMClientImpl::GetInstanceIDData(const std::string& app_id, |
| std::string* instance_id, |
| std::string* extra_data) { |
| DCHECK(instance_id && extra_data); |
| |
| auto iter = instance_id_data_.find(app_id); |
| if (iter == instance_id_data_.end()) |
| return; |
| *instance_id = iter->second.first; |
| *extra_data = iter->second.second; |
| } |
| |
| void GCMClientImpl::AddHeartbeatInterval(const std::string& scope, |
| int interval_ms) { |
| DCHECK(mcs_client_); |
| mcs_client_->AddHeartbeatInterval(scope, interval_ms); |
| } |
| |
| void GCMClientImpl::RemoveHeartbeatInterval(const std::string& scope) { |
| DCHECK(mcs_client_); |
| mcs_client_->RemoveHeartbeatInterval(scope); |
| } |
| |
| void GCMClientImpl::StartCheckin() { |
| // Make sure no checkin is in progress. |
| if (checkin_request_) |
| 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, |
| device_checkin_info_.account_tokens, |
| gservices_settings_.digest(), |
| chrome_build_proto); |
| checkin_request_.reset(new CheckinRequest( |
| gservices_settings_.GetCheckinURL(), request_info, GetGCMBackoffPolicy(), |
| base::Bind(&GCMClientImpl::OnCheckinCompleted, |
| weak_ptr_factory_.GetWeakPtr()), |
| url_loader_factory_, &recorder_)); |
| // Taking a snapshot of the accounts count here, as there might be an asynch |
| // update of the account tokens while checkin is in progress. |
| device_checkin_info_.SnapshotCheckinAccounts(); |
| checkin_request_->Start(); |
| } |
| |
| void GCMClientImpl::OnCheckinCompleted( |
| net::HttpStatusCode response_code, |
| const checkin_proto::AndroidCheckinResponse& checkin_response) { |
| checkin_request_.reset(); |
| |
| if (response_code == net::HTTP_UNAUTHORIZED || |
| response_code == net::HTTP_BAD_REQUEST) { |
| LOG(ERROR) << "Checkin rejected. Resetting GCM Store."; |
| ResetStore(); |
| return; |
| } |
| |
| DCHECK(checkin_response.has_android_id()); |
| DCHECK(checkin_response.has_security_token()); |
| 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 registration 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_->SetLastCheckinInfo( |
| last_checkin_time_, |
| device_checkin_info_.last_checkin_accounts, |
| base::Bind(&GCMClientImpl::SetLastCheckinInfoCallback, |
| 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() || !device_checkin_info_.accounts_set) |
| 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::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&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::SetLastCheckinInfoCallback(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::DefaultStoreCallback(bool success) { |
| DCHECK(success); |
| } |
| |
| void GCMClientImpl::IgnoreWriteResultCallback(bool success) { |
| // TODO(fgorski): Ignoring the write result for now to make sure |
| // sync_intergration_tests are not broken. |
| } |
| |
| void GCMClientImpl::DestroyStoreCallback(bool success) { |
| ResetCache(); |
| |
| if (!success) { |
| LOG(ERROR) << "Failed to destroy GCM store"; |
| RecordResetStoreErrorToUMA(DESTROYING_STORE_FAILED); |
| state_ = UNINITIALIZED; |
| return; |
| } |
| |
| state_ = INITIALIZED; |
| } |
| |
| void GCMClientImpl::ResetStoreCallback(bool success) { |
| // Even an incomplete reset may invalidate registrations, and this might be |
| // the only opportunity to notify the delegate. For example a partial reset |
| // that deletes the "CURRENT" file will cause GCMStoreImpl to consider the DB |
| // to no longer exist, in which case the next load will simply create a new |
| // store rather than resetting it. |
| delegate_->OnStoreReset(); |
| |
| if (!success) { |
| LOG(ERROR) << "Failed to reset GCM store"; |
| RecordResetStoreErrorToUMA(DESTROYING_STORE_FAILED); |
| state_ = UNINITIALIZED; |
| return; |
| } |
| |
| state_ = INITIALIZED; |
| Start(start_mode_); |
| } |
| |
| void GCMClientImpl::Stop() { |
| // TODO(fgorski): Perhaps we should make a distinction between a Stop and a |
| // Shutdown. |
| DVLOG(1) << "Stopping the GCM Client"; |
| ResetCache(); |
| state_ = INITIALIZED; |
| gcm_store_->Close(); |
| } |
| |
| void GCMClientImpl::ResetCache() { |
| weak_ptr_factory_.InvalidateWeakPtrs(); |
| periodic_checkin_ptr_factory_.InvalidateWeakPtrs(); |
| device_checkin_info_.Reset(); |
| connection_factory_.reset(); |
| delegate_->OnDisconnected(); |
| mcs_client_.reset(); |
| checkin_request_.reset(); |
| // Delete all of the pending registration and unregistration requests. |
| pending_registration_requests_.clear(); |
| pending_unregistration_requests_.clear(); |
| } |
| |
| void GCMClientImpl::Register( |
| scoped_refptr<RegistrationInfo> registration_info) { |
| DCHECK_EQ(state_, READY); |
| |
| // Registrations should never pass as an app_id the special category used |
| // internally when registering with a subtype. See security note in |
| // GCMClientImpl::HandleIncomingMessage. |
| CHECK_NE(registration_info->app_id, |
| chrome_build_info_.product_category_for_subtypes); |
| |
| // Find and use the cached registration ID. |
| RegistrationInfoMap::const_iterator registrations_iter = |
| registrations_.find(registration_info); |
| if (registrations_iter != registrations_.end()) { |
| bool matched = true; |
| |
| // For GCM registration, we also match the sender IDs since multiple |
| // registrations are not supported. |
| const GCMRegistrationInfo* gcm_registration_info = |
| GCMRegistrationInfo::FromRegistrationInfo(registration_info.get()); |
| if (gcm_registration_info) { |
| const GCMRegistrationInfo* cached_gcm_registration_info = |
| GCMRegistrationInfo::FromRegistrationInfo( |
| registrations_iter->first.get()); |
| DCHECK(cached_gcm_registration_info); |
| if (gcm_registration_info->sender_ids != |
| cached_gcm_registration_info->sender_ids) { |
| matched = false; |
| // Senders IDs don't match existing registration. |
| RecordRegistrationRequestToUMA( |
| RegistrationCacheStatus::REGISTRATION_FOUND_BUT_SENDERS_DONT_MATCH); |
| } |
| } |
| |
| if (matched) { |
| // Skip registration if token is fresh. |
| base::TimeDelta token_invalidation_period = |
| features::GetTokenInvalidationInterval(); |
| base::TimeDelta time_since_last_validation = |
| clock_->Now() - registrations_iter->first->last_validated; |
| if (token_invalidation_period.is_zero() || |
| time_since_last_validation < token_invalidation_period) { |
| // Token is fresh, or token invalidation is disabled. |
| // Use cached registration. |
| delegate_->OnRegisterFinished(registration_info, |
| registrations_iter->second, SUCCESS); |
| RecordRegistrationRequestToUMA( |
| RegistrationCacheStatus::REGISTRATION_FOUND_AND_FRESH); |
| return; |
| } else { |
| // Token is stale. |
| RecordRegistrationRequestToUMA( |
| RegistrationCacheStatus::REGISTRATION_FOUND_BUT_STALE); |
| } |
| } |
| } else { |
| // New Registration request (no existing registration) |
| RecordRegistrationRequestToUMA( |
| RegistrationCacheStatus::REGISTRATION_NOT_FOUND); |
| } |
| |
| std::unique_ptr<RegistrationRequest::CustomRequestHandler> request_handler; |
| std::string source_to_record; |
| |
| const GCMRegistrationInfo* gcm_registration_info = |
| GCMRegistrationInfo::FromRegistrationInfo(registration_info.get()); |
| if (gcm_registration_info) { |
| std::string senders; |
| for (auto iter = gcm_registration_info->sender_ids.begin(); |
| iter != gcm_registration_info->sender_ids.end(); |
| ++iter) { |
| if (!senders.empty()) |
| senders.append(","); |
| senders.append(*iter); |
| } |
| UMA_HISTOGRAM_COUNTS_1M("GCM.RegistrationSenderIdCount", |
| gcm_registration_info->sender_ids.size()); |
| |
| request_handler.reset(new GCMRegistrationRequestHandler(senders)); |
| source_to_record = senders; |
| } |
| |
| const InstanceIDTokenInfo* instance_id_token_info = |
| InstanceIDTokenInfo::FromRegistrationInfo(registration_info.get()); |
| if (instance_id_token_info) { |
| auto instance_id_iter = instance_id_data_.find(registration_info->app_id); |
| DCHECK(instance_id_iter != instance_id_data_.end()); |
| |
| request_handler.reset(new InstanceIDGetTokenRequestHandler( |
| instance_id_iter->second.first, |
| instance_id_token_info->authorized_entity, |
| instance_id_token_info->scope, |
| ConstructGCMVersion(chrome_build_info_.version), |
| instance_id_token_info->options)); |
| source_to_record = instance_id_token_info->authorized_entity + "/" + |
| instance_id_token_info->scope; |
| } |
| |
| bool use_subtype = instance_id_token_info && |
| InstanceIDUsesSubtypeForAppId(registration_info->app_id); |
| std::string category = use_subtype |
| ? chrome_build_info_.product_category_for_subtypes |
| : registration_info->app_id; |
| std::string subtype = use_subtype ? registration_info->app_id : std::string(); |
| RegistrationRequest::RequestInfo request_info(device_checkin_info_.android_id, |
| device_checkin_info_.secret, |
| category, subtype); |
| |
| std::unique_ptr<RegistrationRequest> registration_request( |
| new RegistrationRequest( |
| gservices_settings_.GetRegistrationURL(), request_info, |
| std::move(request_handler), GetGCMBackoffPolicy(), |
| base::Bind(&GCMClientImpl::OnRegisterCompleted, |
| weak_ptr_factory_.GetWeakPtr(), registration_info), |
| kMaxRegistrationRetries, url_loader_factory_, &recorder_, |
| source_to_record)); |
| registration_request->Start(); |
| pending_registration_requests_.insert( |
| std::make_pair(registration_info, std::move(registration_request))); |
| } |
| |
| void GCMClientImpl::OnRegisterCompleted( |
| scoped_refptr<RegistrationInfo> registration_info, |
| RegistrationRequest::Status status, |
| const std::string& registration_id) { |
| DCHECK(delegate_); |
| |
| Result result; |
| PendingRegistrationRequests::const_iterator iter = |
| pending_registration_requests_.find(registration_info); |
| if (iter == pending_registration_requests_.end()) { |
| result = UNKNOWN_ERROR; |
| } else if (status == RegistrationRequest::INVALID_SENDER) { |
| result = INVALID_PARAMETER; |
| } else if (registration_id.empty()) { |
| // All other errors are currently treated as SERVER_ERROR (including |
| // REACHED_MAX_RETRIES due to the device being offline!). |
| result = SERVER_ERROR; |
| } else { |
| result = SUCCESS; |
| } |
| |
| if (result == SUCCESS) { |
| // Cache it. |
| // Note that the existing cached record has to be removed first because |
| // otherwise the key value in registrations_ will not be updated. For GCM |
| // registrations, the key consists of pair of app_id and sender_ids though |
| // only app_id is used in the key comparison. |
| registrations_.erase(registration_info); |
| registration_info->last_validated = clock_->Now(); |
| registrations_[registration_info] = registration_id; |
| |
| // Save it in the persistent store. |
| gcm_store_->AddRegistration( |
| registration_info->GetSerializedKey(), |
| registration_info->GetSerializedValue(registration_id), |
| base::Bind(&GCMClientImpl::UpdateRegistrationCallback, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| delegate_->OnRegisterFinished( |
| registration_info, |
| result == SUCCESS ? registration_id : std::string(), |
| result); |
| |
| if (iter != pending_registration_requests_.end()) |
| pending_registration_requests_.erase(iter); |
| } |
| |
| bool GCMClientImpl::ValidateRegistration( |
| scoped_refptr<RegistrationInfo> registration_info, |
| const std::string& registration_id) { |
| DCHECK_EQ(state_, READY); |
| |
| // Must have a cached registration. |
| RegistrationInfoMap::const_iterator registrations_iter = |
| registrations_.find(registration_info); |
| if (registrations_iter == registrations_.end()) |
| return false; |
| |
| // Cached registration ID must match. |
| const std::string& cached_registration_id = registrations_iter->second; |
| if (registration_id != cached_registration_id) |
| return false; |
| |
| // For GCM registration, we also match the sender IDs since multiple |
| // registrations are not supported. |
| const GCMRegistrationInfo* gcm_registration_info = |
| GCMRegistrationInfo::FromRegistrationInfo(registration_info.get()); |
| if (gcm_registration_info) { |
| const GCMRegistrationInfo* cached_gcm_registration_info = |
| GCMRegistrationInfo::FromRegistrationInfo( |
| registrations_iter->first.get()); |
| DCHECK(cached_gcm_registration_info); |
| if (gcm_registration_info->sender_ids != |
| cached_gcm_registration_info->sender_ids) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| void GCMClientImpl::Unregister( |
| scoped_refptr<RegistrationInfo> registration_info) { |
| DCHECK_EQ(state_, READY); |
| |
| std::unique_ptr<UnregistrationRequest::CustomRequestHandler> request_handler; |
| std::string source_to_record; |
| |
| const GCMRegistrationInfo* gcm_registration_info = |
| GCMRegistrationInfo::FromRegistrationInfo(registration_info.get()); |
| if (gcm_registration_info) { |
| request_handler.reset( |
| new GCMUnregistrationRequestHandler(registration_info->app_id)); |
| } |
| |
| const InstanceIDTokenInfo* instance_id_token_info = |
| InstanceIDTokenInfo::FromRegistrationInfo(registration_info.get()); |
| if (instance_id_token_info) { |
| auto instance_id_iter = instance_id_data_.find(registration_info->app_id); |
| if (instance_id_iter == instance_id_data_.end()) { |
| // This should not be reached since we should not delete tokens when |
| // an InstanceID has not been created yet. |
| NOTREACHED(); |
| return; |
| } |
| |
| request_handler.reset(new InstanceIDDeleteTokenRequestHandler( |
| instance_id_iter->second.first, |
| instance_id_token_info->authorized_entity, |
| instance_id_token_info->scope, |
| ConstructGCMVersion(chrome_build_info_.version))); |
| source_to_record = instance_id_token_info->authorized_entity + "/" + |
| instance_id_token_info->scope; |
| } |
| |
| // Remove the registration/token(s) from the cache and the store. |
| // TODO(jianli): Remove it only when the request is successful. |
| if (instance_id_token_info && |
| instance_id_token_info->authorized_entity == "*" && |
| instance_id_token_info->scope == "*") { |
| // If authorized_entity and scope are '*', find and remove all associated |
| // tokens. |
| bool token_found = false; |
| for (auto iter = registrations_.begin(); |
| iter != registrations_.end();) { |
| InstanceIDTokenInfo* cached_instance_id_token_info = |
| InstanceIDTokenInfo::FromRegistrationInfo(iter->first.get()); |
| if (cached_instance_id_token_info && |
| cached_instance_id_token_info->app_id == registration_info->app_id) { |
| token_found = true; |
| gcm_store_->RemoveRegistration( |
| cached_instance_id_token_info->GetSerializedKey(), |
| base::Bind(&GCMClientImpl::UpdateRegistrationCallback, |
| weak_ptr_factory_.GetWeakPtr())); |
| registrations_.erase(iter++); |
| } else { |
| ++iter; |
| } |
| } |
| |
| // If no token is found for the Instance ID, don't need to unregister |
| // since the Instance ID is not sent to the server yet. |
| if (!token_found) { |
| OnUnregisterCompleted(registration_info, |
| UnregistrationRequest::SUCCESS); |
| return; |
| } |
| } else { |
| auto iter = registrations_.find(registration_info); |
| if (iter == registrations_.end()) { |
| delegate_->OnUnregisterFinished(registration_info, INVALID_PARAMETER); |
| return; |
| } |
| registrations_.erase(iter); |
| |
| gcm_store_->RemoveRegistration( |
| registration_info->GetSerializedKey(), |
| base::Bind(&GCMClientImpl::UpdateRegistrationCallback, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| bool use_subtype = instance_id_token_info && |
| InstanceIDUsesSubtypeForAppId(registration_info->app_id); |
| std::string category = use_subtype |
| ? chrome_build_info_.product_category_for_subtypes |
| : registration_info->app_id; |
| std::string subtype = use_subtype ? registration_info->app_id : std::string(); |
| UnregistrationRequest::RequestInfo request_info( |
| device_checkin_info_.android_id, device_checkin_info_.secret, category, |
| subtype); |
| |
| std::unique_ptr<UnregistrationRequest> unregistration_request( |
| new UnregistrationRequest( |
| gservices_settings_.GetRegistrationURL(), request_info, |
| std::move(request_handler), GetGCMBackoffPolicy(), |
| base::Bind(&GCMClientImpl::OnUnregisterCompleted, |
| weak_ptr_factory_.GetWeakPtr(), registration_info), |
| kMaxUnregistrationRetries, url_loader_factory_, &recorder_, |
| source_to_record)); |
| unregistration_request->Start(); |
| pending_unregistration_requests_.insert( |
| std::make_pair(registration_info, std::move(unregistration_request))); |
| } |
| |
| void GCMClientImpl::OnUnregisterCompleted( |
| scoped_refptr<RegistrationInfo> registration_info, |
| UnregistrationRequest::Status status) { |
| DVLOG(1) << "Unregister completed for app: " << registration_info->app_id |
| << " with " << (status ? "success." : "failure."); |
| |
| Result result; |
| switch (status) { |
| case UnregistrationRequest::SUCCESS: |
| result = SUCCESS; |
| break; |
| case UnregistrationRequest::INVALID_PARAMETERS: |
| result = INVALID_PARAMETER; |
| break; |
| default: |
| // All other errors are currently treated as SERVER_ERROR (including |
| // REACHED_MAX_RETRIES due to the device being offline!). |
| result = SERVER_ERROR; |
| break; |
| } |
| delegate_->OnUnregisterFinished(registration_info, result); |
| |
| pending_unregistration_requests_.erase(registration_info); |
| } |
| |
| 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 (auto 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::UNINITIALIZED: |
| return "UNINITIALIZED"; |
| case GCMClientImpl::INITIALIZED: |
| return "INITIALIZED"; |
| case GCMClientImpl::LOADING: |
| return "LOADING"; |
| case GCMClientImpl::LOADED: |
| return "LOADED"; |
| case GCMClientImpl::INITIAL_DEVICE_CHECKIN: |
| return "INITIAL_DEVICE_CHECKIN"; |
| case GCMClientImpl::READY: |
| return "READY"; |
| } |
| NOTREACHED(); |
| return std::string(); |
| } |
| |
| void GCMClientImpl::RecordDecryptionFailure(const std::string& app_id, |
| GCMDecryptionResult result) { |
| recorder_.RecordDecryptionFailure(app_id, result); |
| } |
| |
| void GCMClientImpl::SetRecording(bool recording) { |
| recorder_.set_is_recording(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_ != nullptr; |
| stats.last_checkin = last_checkin_time_; |
| stats.next_checkin = |
| last_checkin_time_ + gservices_settings_.GetCheckinInterval(); |
| if (connection_factory_) |
| stats.connection_state = connection_factory_->GetConnectionStateString(); |
| if (mcs_client_) { |
| 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; |
| if (device_checkin_info_.secret > 0) |
| stats.android_secret = device_checkin_info_.secret; |
| |
| recorder_.CollectActivities(&stats.recorded_activities); |
| |
| for (auto it = registrations_.begin(); it != registrations_.end(); ++it) { |
| stats.registered_app_ids.push_back(it->first->app_id); |
| } |
| 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_t 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. |
| // 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_->OnSendAcknowledged(app_id, message_id); |
| } else { |
| 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); |
| |
| // Copy all the data from the stanza to a MessageData object. When present, |
| // keys like kSubtypeKey, 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(); |
| } |
| |
| std::string subtype; |
| auto subtype_iter = message_data.find(kSubtypeKey); |
| if (subtype_iter != message_data.end()) { |
| subtype = subtype_iter->second; |
| message_data.erase(subtype_iter); |
| } |
| |
| // SECURITY NOTE: Subtypes received from GCM *cannot* be trusted for |
| // registrations without a subtype (as the sender can pass any subtype they |
| // want). They can however be trusted for registrations that are known to have |
| // a subtype (as GCM overwrites anything passed by the sender). |
| // |
| // So a given Chrome profile always passes a fixed string called |
| // |product_category_for_subtypes| (of the form "com.chrome.macosx") as the |
| // category when registering with a subtype, and incoming subtypes are only |
| // trusted for that category. |
| // |
| // TODO(johnme): Remove this check if GCM starts sending the subtype in a |
| // field that's guaranteed to be trusted (b/18198485). |
| // |
| // (On Android, all registrations made by Chrome on behalf of third-party |
| // apps/extensions/websites have always had a subtype, so such a check is not |
| // necessary - or possible, since category is fixed to the true package name). |
| bool subtype_is_trusted = data_message_stanza.category() == |
| chrome_build_info_.product_category_for_subtypes; |
| bool use_subtype = subtype_is_trusted && !subtype.empty(); |
| std::string app_id = use_subtype ? subtype : data_message_stanza.category(); |
| |
| MessageType message_type = DATA_MESSAGE; |
| auto type_iter = message_data.find(kMessageTypeKey); |
| if (type_iter != message_data.end()) { |
| message_type = DecodeMessageType(type_iter->second); |
| message_data.erase(type_iter); |
| } |
| |
| switch (message_type) { |
| case DATA_MESSAGE: |
| HandleIncomingDataMessage(app_id, use_subtype, data_message_stanza, |
| message_data); |
| break; |
| case DELETED_MESSAGES: |
| HandleIncomingDeletedMessages(app_id, data_message_stanza, message_data); |
| break; |
| case SEND_ERROR: |
| HandleIncomingSendError(app_id, data_message_stanza, message_data); |
| break; |
| case UNKNOWN: |
| DVLOG(1) << "Unknown message_type received. Message ignored. " |
| << "App ID: " << app_id << "."; |
| break; |
| } |
| } |
| |
| void GCMClientImpl::HandleIncomingDataMessage( |
| const std::string& app_id, |
| bool was_subtype, |
| const mcs_proto::DataMessageStanza& data_message_stanza, |
| MessageData& message_data) { |
| UMA_HISTOGRAM_BOOLEAN("GCM.DataMessageReceived", true); |
| |
| bool has_collapse_key = |
| data_message_stanza.has_token() && !data_message_stanza.token().empty(); |
| UMA_HISTOGRAM_BOOLEAN("GCM.DataMessageReceivedHasCollapseKey", |
| has_collapse_key); |
| |
| recorder_.RecordDataMessageReceived(app_id, data_message_stanza.from(), |
| data_message_stanza.ByteSize(), |
| GCMStatsRecorder::DATA_MESSAGE); |
| |
| 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; |
| incoming_message.raw_data = data_message_stanza.raw_data(); |
| |
| delegate_->OnMessageReceived(app_id, incoming_message); |
| } |
| |
| void GCMClientImpl::HandleIncomingDeletedMessages( |
| const std::string& app_id, |
| const mcs_proto::DataMessageStanza& data_message_stanza, |
| MessageData& message_data) { |
| int deleted_count = 0; |
| auto count_iter = message_data.find(kDeletedCountKey); |
| if (count_iter != message_data.end()) { |
| if (!base::StringToInt(count_iter->second, &deleted_count)) |
| deleted_count = 0; |
| } |
| UMA_HISTOGRAM_COUNTS_1000("GCM.DeletedMessagesReceived", deleted_count); |
| |
| recorder_.RecordDataMessageReceived(app_id, data_message_stanza.from(), |
| data_message_stanza.ByteSize(), |
| GCMStatsRecorder::DELETED_MESSAGES); |
| delegate_->OnMessagesDeleted(app_id); |
| } |
| |
| void GCMClientImpl::HandleIncomingSendError( |
| const std::string& app_id, |
| 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; |
| |
| auto 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(app_id, data_message_stanza.to(), |
| data_message_stanza.id()); |
| delegate_->OnMessageSendError(app_id, send_error_details); |
| } |
| |
| bool GCMClientImpl::HasStandaloneRegisteredApp() const { |
| if (registrations_.empty()) |
| return false; |
| // Note that account mapper is not counted as a standalone app since it is |
| // automatically started when other app uses GCM. |
| return registrations_.size() > 1 || |
| !ExistsGCMRegistrationInMap(registrations_, kGCMAccountMapperAppId); |
| } |
| |
| } // namespace gcm |