summaryrefslogtreecommitdiff
path: root/chromium/components/gcm_driver/gcm_client_impl.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/components/gcm_driver/gcm_client_impl.cc')
-rw-r--r--chromium/components/gcm_driver/gcm_client_impl.cc1481
1 files changed, 1481 insertions, 0 deletions
diff --git a/chromium/components/gcm_driver/gcm_client_impl.cc b/chromium/components/gcm_driver/gcm_client_impl.cc
new file mode 100644
index 00000000000..016228a0326
--- /dev/null
+++ b/chromium/components/gcm_driver/gcm_client_impl.cc
@@ -0,0 +1,1481 @@
+// 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_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<MCSClient> GCMInternalsBuilder::BuildMCSClient(
+ const std::string& version,
+ base::Clock* clock,
+ ConnectionFactory* connection_factory,
+ GCMStore* gcm_store,
+ scoped_refptr<base::SequencedTaskRunner> io_task_runner,
+ GCMStatsRecorder* recorder) {
+ return std::make_unique<MCSClient>(version, clock, connection_factory,
+ gcm_store, std::move(io_task_runner),
+ recorder);
+}
+
+std::unique_ptr<ConnectionFactory> GCMInternalsBuilder::BuildConnectionFactory(
+ const std::vector<GURL>& endpoints,
+ const net::BackoffEntry::Policy& backoff_policy,
+ base::RepeatingCallback<void(
+ mojo::PendingReceiver<network::mojom::ProxyResolvingSocketFactory>)>
+ get_socket_factory_callback,
+ scoped_refptr<base::SequencedTaskRunner> io_task_runner,
+ GCMStatsRecorder* recorder,
+ network::NetworkConnectionTracker* network_connection_tracker) {
+ return std::make_unique<ConnectionFactoryImpl>(
+ 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<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) {}
+
+GCMClientImpl::~GCMClientImpl() {
+}
+
+void GCMClientImpl::Initialize(
+ const ChromeBuildInfo& chrome_build_info,
+ const base::FilePath& path,
+ bool remove_account_mappings_with_email_key,
+ const scoped_refptr<base::SequencedTaskRunner>& blocking_task_runner,
+ scoped_refptr<base::SequencedTaskRunner> io_task_runner,
+ base::RepeatingCallback<void(
+ mojo::PendingReceiver<network::mojom::ProxyResolvingSocketFactory>)>
+ 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);
+ 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<GCMStoreImpl>(
+ 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<GCMStore::LoadResult> 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<RegistrationInfo> registration =
+ RegistrationInfo::BuildFromString(iter->first, iter->second,
+ &registration_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<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_,
+ 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<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::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<AccountTokenInfo>& 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<base::RetainingOneShotTimer> 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<CheckinRequest>(
+ 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<RegistrationInfo> 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<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);
+ }
+ request_handler = std::make_unique<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 = std::make_unique<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->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<RegistrationRequest> 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<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::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<RegistrationInfo> 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<RegistrationInfo> registration_info) {
+ DCHECK_EQ(state_, READY);
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+
+ 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 = std::make_unique<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 = std::make_unique<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::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<UnregistrationRequest> 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<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);
+ 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<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 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