// 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 #include #include #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_functions.h" #include "base/metrics/histogram_macros.h" #include "base/strings/string_number_conversions.h" #include "base/task/sequenced_task_runner.h" #include "base/task/single_thread_task_runner.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 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 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 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 GCMInternalsBuilder::BuildMCSClient( const std::string& version, base::Clock* clock, ConnectionFactory* connection_factory, GCMStore* gcm_store, scoped_refptr io_task_runner, GCMStatsRecorder* recorder) { return std::make_unique(version, clock, connection_factory, gcm_store, std::move(io_task_runner), recorder); } std::unique_ptr GCMInternalsBuilder::BuildConnectionFactory( const std::vector& endpoints, const net::BackoffEntry::Policy& backoff_policy, base::RepeatingCallback)> get_socket_factory_callback, scoped_refptr io_task_runner, GCMStatsRecorder* recorder, network::NetworkConnectionTracker* network_connection_tracker) { return std::make_unique( endpoints, backoff_policy, std::move(get_socket_factory_callback), std::move(io_task_runner), 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 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) {} GCMClientImpl::~GCMClientImpl() { } void GCMClientImpl::Initialize( const ChromeBuildInfo& chrome_build_info, const base::FilePath& path, bool remove_account_mappings_with_email_key, const scoped_refptr& blocking_task_runner, scoped_refptr io_task_runner, base::RepeatingCallback)> get_socket_factory_callback, const scoped_refptr& url_loader_factory, network::NetworkConnectionTracker* network_connection_tracker, std::unique_ptr encryptor, GCMClient::Delegate* delegate) { DCHECK_EQ(UNINITIALIZED, state_); DCHECK(delegate); DCHECK(io_task_runner); DCHECK(io_task_runner->RunsTasksInCurrentSequence()); 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_ = std::make_unique( path, remove_account_mappings_with_email_key, blocking_task_runner, std::move(encryptor)); delegate_ = delegate; io_task_runner_ = std::move(io_task_runner); recorder_.SetDelegate(this); state_ = INITIALIZED; } void GCMClientImpl::Start(StartMode start_mode) { DCHECK_NE(UNINITIALIZED, state_); DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); 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::BindOnce(&GCMClientImpl::OnLoadCompleted, weak_ptr_factory_.GetWeakPtr())); state_ = LOADING; } void GCMClientImpl::OnLoadCompleted( std::unique_ptr result) { DCHECK_EQ(LOADING, state_); DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); 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::BindOnce(&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 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) { io_task_runner_->PostDelayedTask( FROM_HERE, base::BindOnce(&GCMClientImpl::DestroyStoreWhenNotNeeded, destroying_gcm_store_ptr_factory_.GetWeakPtr()), base::Milliseconds(kDestroyGCMStoreDelayMS)); } return; } StartGCM(); } void GCMClientImpl::StartGCM() { DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); // Taking over the value of account_mappings before passing the ownership of // load result to InitializeMCSClient. std::vector 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 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_, io_task_runner_, &recorder_, network_connection_tracker_); connection_factory_->SetConnectionListener(this); mcs_client_ = internals_builder_->BuildMCSClient( chrome_build_info_.version, clock_, connection_factory_.get(), gcm_store_.get(), io_task_runner_, &recorder_); mcs_client_->Initialize( base::BindRepeating(&GCMClientImpl::OnMCSError, weak_ptr_factory_.GetWeakPtr()), base::BindRepeating(&GCMClientImpl::OnMessageReceivedFromMCS, weak_ptr_factory_.GetWeakPtr()), base::BindRepeating(&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::BindOnce(&GCMClientImpl::SetDeviceCredentialsCallback, weak_ptr_factory_.GetWeakPtr())); OnReady(std::vector(), base::Time()); } void GCMClientImpl::OnReady(const std::vector& 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::BindOnce(&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::BindOnce(&GCMClientImpl::ResetStoreCallback, weak_ptr_factory_.GetWeakPtr())); } void GCMClientImpl::SetAccountTokens( const std::vector& account_tokens) { DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); 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) { DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); gcm_store_->AddAccountMapping( account_mapping, base::BindOnce(&GCMClientImpl::DefaultStoreCallback, weak_ptr_factory_.GetWeakPtr())); } void GCMClientImpl::RemoveAccountMapping(const CoreAccountId& account_id) { DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); gcm_store_->RemoveAccountMapping( account_id, base::BindOnce(&GCMClientImpl::DefaultStoreCallback, weak_ptr_factory_.GetWeakPtr())); } void GCMClientImpl::SetLastTokenFetchTime(const base::Time& time) { DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); gcm_store_->SetLastTokenFetchTime( time, base::BindOnce(&GCMClientImpl::IgnoreWriteResultCallback, weak_ptr_factory_.GetWeakPtr(), /*operation_suffix_for_uma=*/"SetLastTokenFetchTime")); } void GCMClientImpl::UpdateHeartbeatTimer( std::unique_ptr timer) { DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); 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) { DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); instance_id_data_[app_id] = std::make_pair(instance_id, extra_data); // TODO(crbug/1028761): If this call fails, we likely leak a registration // (the one stored in instance_id_data_ would be used for a registration but // not persisted). gcm_store_->AddInstanceIDData( app_id, SerializeInstanceIDData(instance_id, extra_data), base::BindOnce(&GCMClientImpl::IgnoreWriteResultCallback, weak_ptr_factory_.GetWeakPtr(), /*operation_suffix_for_uma=*/"AddInstanceIDData")); } void GCMClientImpl::RemoveInstanceIDData(const std::string& app_id) { DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); instance_id_data_.erase(app_id); gcm_store_->RemoveInstanceIDData( app_id, base::BindOnce(&GCMClientImpl::IgnoreWriteResultCallback, weak_ptr_factory_.GetWeakPtr(), /*operation_suffix_for_uma=*/"RemoveInstanceIDData")); } 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(io_task_runner_->RunsTasksInCurrentSequence()); DCHECK(mcs_client_); mcs_client_->AddHeartbeatInterval(scope, interval_ms); } void GCMClientImpl::RemoveHeartbeatInterval(const std::string& scope) { DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); DCHECK(mcs_client_); mcs_client_->RemoveHeartbeatInterval(scope); } void GCMClientImpl::StartCheckin() { DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); // 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_ = std::make_unique( gservices_settings_.GetCheckinURL(), request_info, GetGCMBackoffPolicy(), base::BindOnce(&GCMClientImpl::OnCheckinCompleted, weak_ptr_factory_.GetWeakPtr()), url_loader_factory_, io_task_runner_, &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) { DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); 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::BindOnce(&GCMClientImpl::SetGServicesSettingsCallback, weak_ptr_factory_.GetWeakPtr())); } last_checkin_time_ = clock_->Now(); gcm_store_->SetLastCheckinInfo( last_checkin_time_, device_checkin_info_.last_checkin_accounts, base::BindOnce(&GCMClientImpl::SetLastCheckinInfoCallback, weak_ptr_factory_.GetWeakPtr())); SchedulePeriodicCheckin(); } } void GCMClientImpl::SetGServicesSettingsCallback(bool success) { DCHECK(success); } void GCMClientImpl::SchedulePeriodicCheckin() { DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); // 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.is_negative()) time_to_next_checkin = base::TimeDelta(); io_task_runner_->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( const std::string& operation_suffix_for_uma, bool success) { // TODO(crbug.com/1081149): Implement proper error handling. // 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() { DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); // 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 registration_info) { DCHECK_EQ(state_, READY); DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); // 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 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); } request_handler = std::make_unique(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 = std::make_unique( 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->time_to_live); 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 registration_request( new RegistrationRequest( gservices_settings_.GetRegistrationURL(), request_info, std::move(request_handler), GetGCMBackoffPolicy(), base::BindOnce(&GCMClientImpl::OnRegisterCompleted, weak_ptr_factory_.GetWeakPtr(), registration_info), kMaxRegistrationRetries, url_loader_factory_, io_task_runner_, &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 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::BindOnce(&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 registration_info, const std::string& registration_id) { DCHECK_EQ(state_, READY); DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); // 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 registration_info) { DCHECK_EQ(state_, READY); DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); std::unique_ptr request_handler; std::string source_to_record; const GCMRegistrationInfo* gcm_registration_info = GCMRegistrationInfo::FromRegistrationInfo(registration_info.get()); if (gcm_registration_info) { request_handler = std::make_unique( 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 = std::make_unique( 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::BindOnce(&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::BindOnce(&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 unregistration_request( new UnregistrationRequest( gservices_settings_.GetRegistrationURL(), request_info, std::move(request_handler), GetGCMBackoffPolicy(), base::BindOnce(&GCMClientImpl::OnUnregisterCompleted, weak_ptr_factory_.GetWeakPtr(), registration_info), kMaxUnregistrationRetries, url_loader_factory_, io_task_runner_, &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 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); DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); 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) { DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); recorder_.RecordDecryptionFailure(app_id, result); } void GCMClientImpl::SetRecording(bool recording) { recorder_.set_is_recording(recording); } void GCMClientImpl::ClearActivityLogs() { DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); recorder_.Clear(); } GCMClient::GCMStatistics GCMClientImpl::GetStatistics() const { DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); 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( 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 or kMessageTypeKey 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(); incoming_message.message_id = data_message_stanza.persistent_id(); 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; send_error_details.message_id = data_message_stanza.persistent_id(); 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