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 14namespace gcm { 15 16namespace { 17 18const char kGCMAccountMapperSenderId[] = "745476177629"; 19const char kGCMAccountMapperAppId[] = "com.google.android.gms"; 20const int kGCMAddMappingMessageTTL = 30 * 60; // 0.5 hours in seconds. 21const int kGCMRemoveMappingMessageTTL = 24 * 60 * 60; // 1 day in seconds. 22const 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. 26const int kGCMUpdateEarlyStartHours = 6; 27const char kRegistrationIdMessgaeKey[] = "id"; 28const char kTokenMessageKey[] = "t"; 29const char kAccountMessageKey[] = "a"; 30const char kRemoveAccountKey[] = "r"; 31const char kRemoveAccountValue[] = "1"; 32 33std::string GenerateMessageID() { 34 return base::GenerateGUID(); 35} 36 37} // namespace 38 39GCMAccountMapper::GCMAccountMapper(GCMDriver* gcm_driver) 40 : gcm_driver_(gcm_driver), 41 clock_(new base::DefaultClock), 42 initialized_(false), 43 weak_ptr_factory_(this) { 44} 45 46GCMAccountMapper::~GCMAccountMapper() { 47} 48 49void 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 58void 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 136void GCMAccountMapper::ShutdownHandler() { 137 gcm_driver_->RemoveAppHandler(kGCMAccountMapperAppId); 138} 139 140void GCMAccountMapper::OnMessage(const std::string& app_id, 141 const GCMClient::IncomingMessage& message) { 142 // Account message does not expect messages right now. 143} 144 145void GCMAccountMapper::OnMessagesDeleted(const std::string& app_id) { 146 // Account message does not expect messages right now. 147} 148 149void 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 185void 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 217bool GCMAccountMapper::CanHandle(const std::string& app_id) const { 218 return app_id.compare(kGCMAccountMapperAppId) == 0; 219} 220 221bool GCMAccountMapper::IsReady() { 222 return initialized_ && !registration_id_.empty(); 223} 224 225void GCMAccountMapper::SendAddMappingMessage(AccountMapping& account_mapping) { 226 CreateAndSendMessage(account_mapping); 227} 228 229void 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 245void 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 268void 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 290void 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 300void 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 313bool 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 321bool 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 329AccountMapping* 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 341GCMAccountMapper::AccountMappings::iterator 342GCMAccountMapper::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 353void GCMAccountMapper::SetClockForTesting(scoped_ptr<base::Clock> clock) { 354 clock_ = clock.Pass(); 355} 356 357} // namespace gcm 358