123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386 |
- // 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_account_mapper.h"
- #include <utility>
- #include "base/bind.h"
- #include "base/guid.h"
- #include "base/metrics/histogram_functions.h"
- #include "base/time/clock.h"
- #include "base/time/default_clock.h"
- #include "components/gcm_driver/gcm_driver_desktop.h"
- #include "google_apis/gcm/engine/gcm_store.h"
- namespace gcm {
- namespace {
- const char kGCMAccountMapperSenderId[] = "745476177629";
- const char kGCMAccountMapperSendTo[] = "google.com";
- const int kGCMAddMappingMessageTTL = 30 * 60; // 0.5 hours in seconds.
- const int kGCMRemoveMappingMessageTTL = 24 * 60 * 60; // 1 day in seconds.
- const int kGCMUpdateIntervalHours = 24;
- // Because adding an account mapping dependents on a fresh OAuth2 token, we
- // allow the update to happen earlier than update due time, if it is within
- // the early start time to take advantage of that token.
- const int kGCMUpdateEarlyStartHours = 6;
- const char kRegistrationIdMessgaeKey[] = "id";
- const char kTokenMessageKey[] = "t";
- const char kAccountMessageKey[] = "a";
- const char kRemoveAccountKey[] = "r";
- const char kRemoveAccountValue[] = "1";
- // Use to handle send to Gaia ID scenario:
- const char kGCMSendToGaiaIdAppIdKey[] = "gcmb";
- std::string GenerateMessageID() {
- return base::GenerateGUID();
- }
- } // namespace
- const char kGCMAccountMapperAppId[] = "com.google.android.gms";
- GCMAccountMapper::GCMAccountMapper(GCMDriver* gcm_driver)
- : gcm_driver_(gcm_driver),
- clock_(base::DefaultClock::GetInstance()),
- initialized_(false) {}
- GCMAccountMapper::~GCMAccountMapper() = default;
- void GCMAccountMapper::Initialize(const AccountMappings& account_mappings,
- const DispatchMessageCallback& callback) {
- DCHECK(!initialized_);
- initialized_ = true;
- accounts_ = account_mappings;
- dispatch_message_callback_ = callback;
- GetRegistration();
- }
- void GCMAccountMapper::SetAccountTokens(
- const std::vector<GCMClient::AccountTokenInfo>& account_tokens) {
- DVLOG(1) << "GCMAccountMapper::SetAccountTokens called with "
- << account_tokens.size() << " accounts.";
- // If account mapper is not ready to handle tasks yet, save the latest
- // account tokens and return.
- if (!IsReady()) {
- pending_account_tokens_ = account_tokens;
- // If mapper is initialized, but still does not have registration ID,
- // maybe the registration gave up. Retrying in case.
- if (initialized_ && gcm_driver_->IsStarted())
- GetRegistration();
- return;
- }
- // Start from removing the old tokens, from all of the known accounts.
- for (auto iter = accounts_.begin(); iter != accounts_.end(); ++iter) {
- iter->access_token.clear();
- }
- // Update the internal collection of mappings with the new tokens.
- for (auto token_iter = account_tokens.begin();
- token_iter != account_tokens.end(); ++token_iter) {
- AccountMapping* account_mapping =
- FindMappingByAccountId(token_iter->account_id);
- if (!account_mapping) {
- AccountMapping new_mapping;
- new_mapping.status = AccountMapping::NEW;
- new_mapping.account_id = token_iter->account_id;
- new_mapping.access_token = token_iter->access_token;
- new_mapping.email = token_iter->email;
- accounts_.push_back(new_mapping);
- } else {
- // Since we got a token for an account, drop the remove message and treat
- // it as mapped.
- if (account_mapping->status == AccountMapping::REMOVING) {
- account_mapping->status = AccountMapping::MAPPED;
- account_mapping->status_change_timestamp = base::Time();
- account_mapping->last_message_id.clear();
- }
- account_mapping->email = token_iter->email;
- account_mapping->access_token = token_iter->access_token;
- }
- }
- // Decide what to do with each account (either start mapping, or start
- // removing).
- for (auto mappings_iter = accounts_.begin(); mappings_iter != accounts_.end();
- ++mappings_iter) {
- if (mappings_iter->access_token.empty()) {
- // Send a remove message if the account was not previously being removed,
- // or it doesn't have a pending message, or the pending message is
- // already expired, but OnSendError event was lost.
- if (mappings_iter->status != AccountMapping::REMOVING ||
- mappings_iter->last_message_id.empty() ||
- IsLastStatusChangeOlderThanTTL(*mappings_iter)) {
- SendRemoveMappingMessage(*mappings_iter);
- }
- } else {
- // A message is sent for all of the mappings considered NEW, or mappings
- // that are ADDING, but have expired message (OnSendError event lost), or
- // for those mapped accounts that can be refreshed.
- if (mappings_iter->status == AccountMapping::NEW ||
- (mappings_iter->status == AccountMapping::ADDING &&
- IsLastStatusChangeOlderThanTTL(*mappings_iter)) ||
- (mappings_iter->status == AccountMapping::MAPPED &&
- CanTriggerUpdate(mappings_iter->status_change_timestamp))) {
- mappings_iter->last_message_id.clear();
- SendAddMappingMessage(*mappings_iter);
- }
- }
- }
- }
- void GCMAccountMapper::ShutdownHandler() {
- initialized_ = false;
- accounts_.clear();
- registration_id_.clear();
- dispatch_message_callback_.Reset();
- }
- void GCMAccountMapper::OnStoreReset() {
- // TODO(crbug.com/661660): Tell server to remove the mapping. But can't use
- // upstream GCM send for that since the store got reset.
- ShutdownHandler();
- }
- void GCMAccountMapper::OnMessage(const std::string& app_id,
- const IncomingMessage& message) {
- DCHECK_EQ(app_id, kGCMAccountMapperAppId);
- // TODO(fgorski): Report Send to Gaia ID failures using UMA.
- base::UmaHistogramBoolean("GCM.AccountMappingMessageReceived", true);
- if (dispatch_message_callback_.is_null()) {
- DVLOG(1) << "dispatch_message_callback_ missing in GCMAccountMapper";
- return;
- }
- auto it = message.data.find(kGCMSendToGaiaIdAppIdKey);
- if (it == message.data.end()) {
- DVLOG(1) << "Send to Gaia ID failure: Embedded app ID missing.";
- return;
- }
- std::string embedded_app_id = it->second;
- if (embedded_app_id.empty()) {
- DVLOG(1) << "Send to Gaia ID failure: Embedded app ID is empty.";
- return;
- }
- // Ensuring the message does not carry the embedded app ID.
- IncomingMessage new_message = message;
- new_message.data.erase(new_message.data.find(kGCMSendToGaiaIdAppIdKey));
- dispatch_message_callback_.Run(embedded_app_id, new_message);
- }
- void GCMAccountMapper::OnMessagesDeleted(const std::string& app_id) {
- // Account message does not expect messages right now.
- }
- void GCMAccountMapper::OnSendError(
- const std::string& app_id,
- const GCMClient::SendErrorDetails& send_error_details) {
- DCHECK_EQ(app_id, kGCMAccountMapperAppId);
- auto account_mapping_it =
- FindMappingByMessageId(send_error_details.message_id);
- if (account_mapping_it == accounts_.end())
- return;
- if (send_error_details.result != GCMClient::TTL_EXCEEDED) {
- DVLOG(1) << "Send error result different than TTL EXCEEDED: "
- << send_error_details.result << ". "
- << "Postponing the retry until a new batch of tokens arrives.";
- return;
- }
- if (account_mapping_it->status == AccountMapping::REMOVING) {
- // Another message to remove mapping can be sent immediately, because TTL
- // for those is one day. No need to back off.
- SendRemoveMappingMessage(*account_mapping_it);
- } else {
- if (account_mapping_it->status == AccountMapping::ADDING) {
- // There is no mapping established, so we can remove the entry.
- // Getting a fresh token will trigger a new attempt.
- gcm_driver_->RemoveAccountMapping(account_mapping_it->account_id);
- accounts_.erase(account_mapping_it);
- } else {
- // Account is already MAPPED, we have to wait for another token.
- account_mapping_it->last_message_id.clear();
- gcm_driver_->UpdateAccountMapping(*account_mapping_it);
- }
- }
- }
- void GCMAccountMapper::OnSendAcknowledged(const std::string& app_id,
- const std::string& message_id) {
- DCHECK_EQ(app_id, kGCMAccountMapperAppId);
- auto account_mapping_it = FindMappingByMessageId(message_id);
- DVLOG(1) << "OnSendAcknowledged with message ID: " << message_id;
- if (account_mapping_it == accounts_.end())
- return;
- // Here is where we advance a status of a mapping and persist or remove.
- if (account_mapping_it->status == AccountMapping::REMOVING) {
- // Message removing the account has been confirmed by the GCM, we can remove
- // all the information related to the account (from memory and store).
- gcm_driver_->RemoveAccountMapping(account_mapping_it->account_id);
- accounts_.erase(account_mapping_it);
- } else {
- // Mapping status is ADDING only when it is a first time mapping.
- DCHECK(account_mapping_it->status == AccountMapping::ADDING ||
- account_mapping_it->status == AccountMapping::MAPPED);
- // Account is marked as mapped with the current time.
- account_mapping_it->status = AccountMapping::MAPPED;
- account_mapping_it->status_change_timestamp = clock_->Now();
- // There is no pending message for the account.
- account_mapping_it->last_message_id.clear();
- gcm_driver_->UpdateAccountMapping(*account_mapping_it);
- }
- }
- bool GCMAccountMapper::CanHandle(const std::string& app_id) const {
- return app_id.compare(kGCMAccountMapperAppId) == 0;
- }
- bool GCMAccountMapper::IsReady() {
- return initialized_ && gcm_driver_->IsStarted() && !registration_id_.empty();
- }
- void GCMAccountMapper::SendAddMappingMessage(AccountMapping& account_mapping) {
- CreateAndSendMessage(account_mapping);
- }
- void GCMAccountMapper::SendRemoveMappingMessage(
- AccountMapping& account_mapping) {
- // We want to persist an account that is being removed as quickly as possible
- // as well as clean up the last message information.
- if (account_mapping.status != AccountMapping::REMOVING) {
- account_mapping.status = AccountMapping::REMOVING;
- account_mapping.status_change_timestamp = clock_->Now();
- }
- account_mapping.last_message_id.clear();
- gcm_driver_->UpdateAccountMapping(account_mapping);
- CreateAndSendMessage(account_mapping);
- }
- void GCMAccountMapper::CreateAndSendMessage(
- const AccountMapping& account_mapping) {
- OutgoingMessage outgoing_message;
- outgoing_message.id = GenerateMessageID();
- outgoing_message.data[kRegistrationIdMessgaeKey] = registration_id_;
- outgoing_message.data[kAccountMessageKey] = account_mapping.email;
- if (account_mapping.status == AccountMapping::REMOVING) {
- outgoing_message.time_to_live = kGCMRemoveMappingMessageTTL;
- outgoing_message.data[kRemoveAccountKey] = kRemoveAccountValue;
- } else {
- outgoing_message.data[kTokenMessageKey] = account_mapping.access_token;
- outgoing_message.time_to_live = kGCMAddMappingMessageTTL;
- }
- gcm_driver_->Send(kGCMAccountMapperAppId, kGCMAccountMapperSendTo,
- outgoing_message,
- base::BindOnce(&GCMAccountMapper::OnSendFinished,
- weak_ptr_factory_.GetWeakPtr(),
- account_mapping.account_id));
- }
- void GCMAccountMapper::OnSendFinished(const CoreAccountId& account_id,
- const std::string& message_id,
- GCMClient::Result result) {
- // TODO(fgorski): Add another attempt, in case the QUEUE is not full.
- if (result != GCMClient::SUCCESS)
- return;
- AccountMapping* account_mapping = FindMappingByAccountId(account_id);
- DCHECK(account_mapping);
- // If we are dealing with account with status NEW, it is the first time
- // mapping, and we should mark it as ADDING.
- if (account_mapping->status == AccountMapping::NEW) {
- account_mapping->status = AccountMapping::ADDING;
- account_mapping->status_change_timestamp = clock_->Now();
- }
- account_mapping->last_message_id = message_id;
- gcm_driver_->UpdateAccountMapping(*account_mapping);
- }
- void GCMAccountMapper::GetRegistration() {
- DCHECK(registration_id_.empty());
- std::vector<std::string> sender_ids;
- sender_ids.push_back(kGCMAccountMapperSenderId);
- gcm_driver_->Register(kGCMAccountMapperAppId, sender_ids,
- base::BindOnce(&GCMAccountMapper::OnRegisterFinished,
- weak_ptr_factory_.GetWeakPtr()));
- }
- void GCMAccountMapper::OnRegisterFinished(const std::string& registration_id,
- GCMClient::Result result) {
- if (result == GCMClient::SUCCESS)
- registration_id_ = registration_id;
- if (IsReady()) {
- if (!pending_account_tokens_.empty()) {
- SetAccountTokens(pending_account_tokens_);
- pending_account_tokens_.clear();
- }
- }
- }
- bool GCMAccountMapper::CanTriggerUpdate(
- const base::Time& last_update_time) const {
- return last_update_time +
- base::Hours(kGCMUpdateIntervalHours - kGCMUpdateEarlyStartHours) <
- clock_->Now();
- }
- bool GCMAccountMapper::IsLastStatusChangeOlderThanTTL(
- const AccountMapping& account_mapping) const {
- int ttl_seconds = account_mapping.status == AccountMapping::REMOVING ?
- kGCMRemoveMappingMessageTTL : kGCMAddMappingMessageTTL;
- return account_mapping.status_change_timestamp + base::Seconds(ttl_seconds) <
- clock_->Now();
- }
- AccountMapping* GCMAccountMapper::FindMappingByAccountId(
- const CoreAccountId& account_id) {
- for (auto iter = accounts_.begin(); iter != accounts_.end(); ++iter) {
- if (iter->account_id == account_id)
- return &*iter;
- }
- return nullptr;
- }
- GCMAccountMapper::AccountMappings::iterator
- GCMAccountMapper::FindMappingByMessageId(const std::string& message_id) {
- for (auto iter = accounts_.begin(); iter != accounts_.end(); ++iter) {
- if (iter->last_message_id == message_id)
- return iter;
- }
- return accounts_.end();
- }
- void GCMAccountMapper::SetClockForTesting(base::Clock* clock) {
- clock_ = clock;
- }
- } // namespace gcm
|