Home | History | Annotate | Download | only in gcm_driver
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "components/gcm_driver/gcm_account_mapper.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/guid.h"
      9 #include "base/time/clock.h"
     10 #include "base/time/default_clock.h"
     11 #include "components/gcm_driver/gcm_driver_desktop.h"
     12 #include "google_apis/gcm/engine/gcm_store.h"
     13 
     14 namespace gcm {
     15 
     16 namespace {
     17 
     18 const char kGCMAccountMapperSenderId[] = "745476177629";
     19 const char kGCMAccountMapperAppId[] = "com.google.android.gms";
     20 const int kGCMAddMappingMessageTTL = 30 * 60;  // 0.5 hours in seconds.
     21 const int kGCMRemoveMappingMessageTTL = 24 * 60 * 60;  // 1 day in seconds.
     22 const int kGCMUpdateIntervalHours = 24;
     23 // Because adding an account mapping dependents on a fresh OAuth2 token, we
     24 // allow the update to happen earlier than update due time, if it is within
     25 // the early start time to take advantage of that token.
     26 const int kGCMUpdateEarlyStartHours = 6;
     27 const char kRegistrationIdMessgaeKey[] = "id";
     28 const char kTokenMessageKey[] = "t";
     29 const char kAccountMessageKey[] = "a";
     30 const char kRemoveAccountKey[] = "r";
     31 const char kRemoveAccountValue[] = "1";
     32 
     33 std::string GenerateMessageID() {
     34   return base::GenerateGUID();
     35 }
     36 
     37 }  // namespace
     38 
     39 GCMAccountMapper::GCMAccountMapper(GCMDriver* gcm_driver)
     40     : gcm_driver_(gcm_driver),
     41       clock_(new base::DefaultClock),
     42       initialized_(false),
     43       weak_ptr_factory_(this) {
     44 }
     45 
     46 GCMAccountMapper::~GCMAccountMapper() {
     47 }
     48 
     49 void GCMAccountMapper::Initialize(
     50     const std::vector<AccountMapping>& account_mappings) {
     51   DCHECK(!initialized_);
     52   initialized_ = true;
     53   accounts_ = account_mappings;
     54   gcm_driver_->AddAppHandler(kGCMAccountMapperAppId, this);
     55   GetRegistration();
     56 }
     57 
     58 void GCMAccountMapper::SetAccountTokens(
     59     const std::vector<GCMClient::AccountTokenInfo> account_tokens) {
     60   // If account mapper is not ready to handle tasks yet, save the latest
     61   // account tokens and return.
     62   if (!IsReady()) {
     63     pending_account_tokens_ = account_tokens;
     64     // If mapper is initialized, but still does not have registration ID,
     65     // maybe the registration gave up. Retrying in case.
     66     if (initialized_)
     67       GetRegistration();
     68     return;
     69   }
     70 
     71   // Start from removing the old tokens, from all of the known accounts.
     72   for (AccountMappings::iterator iter = accounts_.begin();
     73        iter != accounts_.end();
     74        ++iter) {
     75     iter->access_token.clear();
     76   }
     77 
     78   // Update the internal collection of mappings with the new tokens.
     79   for (std::vector<GCMClient::AccountTokenInfo>::const_iterator token_iter =
     80            account_tokens.begin();
     81        token_iter != account_tokens.end();
     82        ++token_iter) {
     83     AccountMapping* account_mapping =
     84         FindMappingByAccountId(token_iter->account_id);
     85     if (!account_mapping) {
     86       AccountMapping new_mapping;
     87       new_mapping.status = AccountMapping::NEW;
     88       new_mapping.account_id = token_iter->account_id;
     89       new_mapping.access_token = token_iter->access_token;
     90       new_mapping.email = token_iter->email;
     91       accounts_.push_back(new_mapping);
     92     } else {
     93       // Since we got a token for an account, drop the remove message and treat
     94       // it as mapped.
     95       if (account_mapping->status == AccountMapping::REMOVING) {
     96         account_mapping->status = AccountMapping::MAPPED;
     97         account_mapping->status_change_timestamp = base::Time();
     98         account_mapping->last_message_id.clear();
     99       }
    100 
    101       account_mapping->email = token_iter->email;
    102       account_mapping->access_token = token_iter->access_token;
    103     }
    104   }
    105 
    106   // Decide what to do with each account (either start mapping, or start
    107   // removing).
    108   for (AccountMappings::iterator mappings_iter = accounts_.begin();
    109        mappings_iter != accounts_.end();
    110        ++mappings_iter) {
    111     if (mappings_iter->access_token.empty()) {
    112       // Send a remove message if the account was not previously being removed,
    113       // or it doesn't have a pending message, or the pending message is
    114       // already expired, but OnSendError event was lost.
    115       if (mappings_iter->status != AccountMapping::REMOVING ||
    116           mappings_iter->last_message_id.empty() ||
    117           IsLastStatusChangeOlderThanTTL(*mappings_iter)) {
    118         SendRemoveMappingMessage(*mappings_iter);
    119       }
    120     } else {
    121       // A message is sent for all of the mappings considered NEW, or mappings
    122       // that are ADDING, but have expired message (OnSendError event lost), or
    123       // for those mapped accounts that can be refreshed.
    124       if (mappings_iter->status == AccountMapping::NEW ||
    125           (mappings_iter->status == AccountMapping::ADDING &&
    126            IsLastStatusChangeOlderThanTTL(*mappings_iter)) ||
    127           (mappings_iter->status == AccountMapping::MAPPED &&
    128            CanTriggerUpdate(mappings_iter->status_change_timestamp))) {
    129         mappings_iter->last_message_id.clear();
    130         SendAddMappingMessage(*mappings_iter);
    131       }
    132     }
    133   }
    134 }
    135 
    136 void GCMAccountMapper::ShutdownHandler() {
    137   gcm_driver_->RemoveAppHandler(kGCMAccountMapperAppId);
    138 }
    139 
    140 void GCMAccountMapper::OnMessage(const std::string& app_id,
    141                                  const GCMClient::IncomingMessage& message) {
    142   // Account message does not expect messages right now.
    143 }
    144 
    145 void GCMAccountMapper::OnMessagesDeleted(const std::string& app_id) {
    146   // Account message does not expect messages right now.
    147 }
    148 
    149 void GCMAccountMapper::OnSendError(
    150     const std::string& app_id,
    151     const GCMClient::SendErrorDetails& send_error_details) {
    152   DCHECK_EQ(app_id, kGCMAccountMapperAppId);
    153 
    154   AccountMappings::iterator account_mapping_it =
    155       FindMappingByMessageId(send_error_details.message_id);
    156 
    157   if (account_mapping_it == accounts_.end())
    158     return;
    159 
    160   if (send_error_details.result != GCMClient::TTL_EXCEEDED) {
    161     DVLOG(1) << "Send error result different than TTL EXCEEDED: "
    162              << send_error_details.result << ". "
    163              << "Postponing the retry until a new batch of tokens arrives.";
    164     return;
    165   }
    166 
    167   if (account_mapping_it->status == AccountMapping::REMOVING) {
    168     // Another message to remove mapping can be sent immediately, because TTL
    169     // for those is one day. No need to back off.
    170     SendRemoveMappingMessage(*account_mapping_it);
    171   } else {
    172     if (account_mapping_it->status == AccountMapping::ADDING) {
    173       // There is no mapping established, so we can remove the entry.
    174       // Getting a fresh token will trigger a new attempt.
    175       gcm_driver_->RemoveAccountMapping(account_mapping_it->account_id);
    176       accounts_.erase(account_mapping_it);
    177     } else {
    178       // Account is already MAPPED, we have to wait for another token.
    179       account_mapping_it->last_message_id.clear();
    180       gcm_driver_->UpdateAccountMapping(*account_mapping_it);
    181     }
    182   }
    183 }
    184 
    185 void GCMAccountMapper::OnSendAcknowledged(const std::string& app_id,
    186                                           const std::string& message_id) {
    187   DCHECK_EQ(app_id, kGCMAccountMapperAppId);
    188   AccountMappings::iterator account_mapping_it =
    189       FindMappingByMessageId(message_id);
    190 
    191   DVLOG(1) << "OnSendAcknowledged with message ID: " << message_id;
    192 
    193   if (account_mapping_it == accounts_.end())
    194     return;
    195 
    196   // Here is where we advance a status of a mapping and persist or remove.
    197   if (account_mapping_it->status == AccountMapping::REMOVING) {
    198     // Message removing the account has been confirmed by the GCM, we can remove
    199     // all the information related to the account (from memory and store).
    200     gcm_driver_->RemoveAccountMapping(account_mapping_it->account_id);
    201     accounts_.erase(account_mapping_it);
    202   } else {
    203     // Mapping status is ADDING only when it is a first time mapping.
    204     DCHECK(account_mapping_it->status == AccountMapping::ADDING ||
    205            account_mapping_it->status == AccountMapping::MAPPED);
    206 
    207     // Account is marked as mapped with the current time.
    208     account_mapping_it->status = AccountMapping::MAPPED;
    209     account_mapping_it->status_change_timestamp = clock_->Now();
    210     // There is no pending message for the account.
    211     account_mapping_it->last_message_id.clear();
    212 
    213     gcm_driver_->UpdateAccountMapping(*account_mapping_it);
    214   }
    215 }
    216 
    217 bool GCMAccountMapper::CanHandle(const std::string& app_id) const {
    218   return app_id.compare(kGCMAccountMapperAppId) == 0;
    219 }
    220 
    221 bool GCMAccountMapper::IsReady() {
    222   return initialized_ && !registration_id_.empty();
    223 }
    224 
    225 void GCMAccountMapper::SendAddMappingMessage(AccountMapping& account_mapping) {
    226   CreateAndSendMessage(account_mapping);
    227 }
    228 
    229 void GCMAccountMapper::SendRemoveMappingMessage(
    230     AccountMapping& account_mapping) {
    231   // We want to persist an account that is being removed as quickly as possible
    232   // as well as clean up the last message information.
    233   if (account_mapping.status != AccountMapping::REMOVING) {
    234     account_mapping.status = AccountMapping::REMOVING;
    235     account_mapping.status_change_timestamp = clock_->Now();
    236   }
    237 
    238   account_mapping.last_message_id.clear();
    239 
    240   gcm_driver_->UpdateAccountMapping(account_mapping);
    241 
    242   CreateAndSendMessage(account_mapping);
    243 }
    244 
    245 void GCMAccountMapper::CreateAndSendMessage(
    246     const AccountMapping& account_mapping) {
    247   GCMClient::OutgoingMessage outgoing_message;
    248   outgoing_message.id = GenerateMessageID();
    249   outgoing_message.data[kRegistrationIdMessgaeKey] = registration_id_;
    250   outgoing_message.data[kAccountMessageKey] = account_mapping.email;
    251 
    252   if (account_mapping.status == AccountMapping::REMOVING) {
    253     outgoing_message.time_to_live = kGCMRemoveMappingMessageTTL;
    254     outgoing_message.data[kRemoveAccountKey] = kRemoveAccountValue;
    255   } else {
    256     outgoing_message.data[kTokenMessageKey] = account_mapping.access_token;
    257     outgoing_message.time_to_live = kGCMAddMappingMessageTTL;
    258   }
    259 
    260   gcm_driver_->Send(kGCMAccountMapperAppId,
    261                     kGCMAccountMapperSenderId,
    262                     outgoing_message,
    263                     base::Bind(&GCMAccountMapper::OnSendFinished,
    264                                weak_ptr_factory_.GetWeakPtr(),
    265                                account_mapping.account_id));
    266 }
    267 
    268 void GCMAccountMapper::OnSendFinished(const std::string& account_id,
    269                                       const std::string& message_id,
    270                                       GCMClient::Result result) {
    271   // TODO(fgorski): Add another attempt, in case the QUEUE is not full.
    272   if (result != GCMClient::SUCCESS)
    273     return;
    274 
    275   AccountMapping* account_mapping = FindMappingByAccountId(account_id);
    276   DCHECK(account_mapping);
    277 
    278   // If we are dealing with account with status NEW, it is the first time
    279   // mapping, and we should mark it as ADDING.
    280   if (account_mapping->status == AccountMapping::NEW) {
    281     account_mapping->status = AccountMapping::ADDING;
    282     account_mapping->status_change_timestamp = clock_->Now();
    283   }
    284 
    285   account_mapping->last_message_id = message_id;
    286 
    287   gcm_driver_->UpdateAccountMapping(*account_mapping);
    288 }
    289 
    290 void GCMAccountMapper::GetRegistration() {
    291   DCHECK(registration_id_.empty());
    292   std::vector<std::string> sender_ids;
    293   sender_ids.push_back(kGCMAccountMapperSenderId);
    294   gcm_driver_->Register(kGCMAccountMapperAppId,
    295                         sender_ids,
    296                         base::Bind(&GCMAccountMapper::OnRegisterFinished,
    297                                    weak_ptr_factory_.GetWeakPtr()));
    298 }
    299 
    300 void GCMAccountMapper::OnRegisterFinished(const std::string& registration_id,
    301                                           GCMClient::Result result) {
    302   if (result == GCMClient::SUCCESS)
    303     registration_id_ = registration_id;
    304 
    305   if (IsReady()) {
    306     if (!pending_account_tokens_.empty()) {
    307       SetAccountTokens(pending_account_tokens_);
    308       pending_account_tokens_.clear();
    309     }
    310   }
    311 }
    312 
    313 bool GCMAccountMapper::CanTriggerUpdate(
    314     const base::Time& last_update_time) const {
    315   return last_update_time +
    316              base::TimeDelta::FromHours(kGCMUpdateIntervalHours -
    317                                         kGCMUpdateEarlyStartHours) <
    318          clock_->Now();
    319 }
    320 
    321 bool GCMAccountMapper::IsLastStatusChangeOlderThanTTL(
    322     const AccountMapping& account_mapping) const {
    323   int ttl_seconds = account_mapping.status == AccountMapping::REMOVING ?
    324       kGCMRemoveMappingMessageTTL : kGCMAddMappingMessageTTL;
    325   return account_mapping.status_change_timestamp +
    326       base::TimeDelta::FromSeconds(ttl_seconds) < clock_->Now();
    327 }
    328 
    329 AccountMapping* GCMAccountMapper::FindMappingByAccountId(
    330     const std::string& account_id) {
    331   for (AccountMappings::iterator iter = accounts_.begin();
    332        iter != accounts_.end();
    333        ++iter) {
    334     if (iter->account_id == account_id)
    335       return &*iter;
    336   }
    337 
    338   return NULL;
    339 }
    340 
    341 GCMAccountMapper::AccountMappings::iterator
    342 GCMAccountMapper::FindMappingByMessageId(const std::string& message_id) {
    343   for (std::vector<AccountMapping>::iterator iter = accounts_.begin();
    344        iter != accounts_.end();
    345        ++iter) {
    346     if (iter->last_message_id == message_id)
    347       return iter;
    348   }
    349 
    350   return accounts_.end();
    351 }
    352 
    353 void GCMAccountMapper::SetClockForTesting(scoped_ptr<base::Clock> clock) {
    354   clock_ = clock.Pass();
    355 }
    356 
    357 }  // namespace gcm
    358