Roll src/third_party/WebKit f36d5e0:68b67cd (svn 193299:193303)
[chromium-blink-merge.git] / components / gcm_driver / gcm_account_mapper.cc
blob7caeaba8597b655c2e0704fac5898ead0519ba71
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.
5 #include "components/gcm_driver/gcm_account_mapper.h"
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"
14 namespace gcm {
16 namespace {
18 const char kGCMAccountMapperSenderId[] = "745476177629";
19 const char kGCMAccountMapperSendTo[] = "google.com";
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 // Use to handle send to Gaia ID scenario:
33 const char kGCMSendToGaiaIdAppIdKey[] = "gcmb";
36 std::string GenerateMessageID() {
37 return base::GenerateGUID();
40 } // namespace
42 const char kGCMAccountMapperAppId[] = "com.google.android.gms";
44 GCMAccountMapper::GCMAccountMapper(GCMDriver* gcm_driver)
45 : gcm_driver_(gcm_driver),
46 clock_(new base::DefaultClock),
47 initialized_(false),
48 weak_ptr_factory_(this) {
51 GCMAccountMapper::~GCMAccountMapper() {
54 void GCMAccountMapper::Initialize(const AccountMappings& account_mappings,
55 const DispatchMessageCallback& callback) {
56 DCHECK(!initialized_);
57 initialized_ = true;
58 accounts_ = account_mappings;
59 dispatch_message_callback_ = callback;
60 GetRegistration();
63 void GCMAccountMapper::SetAccountTokens(
64 const std::vector<GCMClient::AccountTokenInfo>& account_tokens) {
65 DVLOG(1) << "GCMAccountMapper::SetAccountTokens called with "
66 << account_tokens.size() << " accounts.";
68 // If account mapper is not ready to handle tasks yet, save the latest
69 // account tokens and return.
70 if (!IsReady()) {
71 pending_account_tokens_ = account_tokens;
72 // If mapper is initialized, but still does not have registration ID,
73 // maybe the registration gave up. Retrying in case.
74 if (initialized_ && gcm_driver_->IsStarted())
75 GetRegistration();
76 return;
79 // Start from removing the old tokens, from all of the known accounts.
80 for (AccountMappings::iterator iter = accounts_.begin();
81 iter != accounts_.end();
82 ++iter) {
83 iter->access_token.clear();
86 // Update the internal collection of mappings with the new tokens.
87 for (std::vector<GCMClient::AccountTokenInfo>::const_iterator token_iter =
88 account_tokens.begin();
89 token_iter != account_tokens.end();
90 ++token_iter) {
91 AccountMapping* account_mapping =
92 FindMappingByAccountId(token_iter->account_id);
93 if (!account_mapping) {
94 AccountMapping new_mapping;
95 new_mapping.status = AccountMapping::NEW;
96 new_mapping.account_id = token_iter->account_id;
97 new_mapping.access_token = token_iter->access_token;
98 new_mapping.email = token_iter->email;
99 accounts_.push_back(new_mapping);
100 } else {
101 // Since we got a token for an account, drop the remove message and treat
102 // it as mapped.
103 if (account_mapping->status == AccountMapping::REMOVING) {
104 account_mapping->status = AccountMapping::MAPPED;
105 account_mapping->status_change_timestamp = base::Time();
106 account_mapping->last_message_id.clear();
109 account_mapping->email = token_iter->email;
110 account_mapping->access_token = token_iter->access_token;
114 // Decide what to do with each account (either start mapping, or start
115 // removing).
116 for (AccountMappings::iterator mappings_iter = accounts_.begin();
117 mappings_iter != accounts_.end();
118 ++mappings_iter) {
119 if (mappings_iter->access_token.empty()) {
120 // Send a remove message if the account was not previously being removed,
121 // or it doesn't have a pending message, or the pending message is
122 // already expired, but OnSendError event was lost.
123 if (mappings_iter->status != AccountMapping::REMOVING ||
124 mappings_iter->last_message_id.empty() ||
125 IsLastStatusChangeOlderThanTTL(*mappings_iter)) {
126 SendRemoveMappingMessage(*mappings_iter);
128 } else {
129 // A message is sent for all of the mappings considered NEW, or mappings
130 // that are ADDING, but have expired message (OnSendError event lost), or
131 // for those mapped accounts that can be refreshed.
132 if (mappings_iter->status == AccountMapping::NEW ||
133 (mappings_iter->status == AccountMapping::ADDING &&
134 IsLastStatusChangeOlderThanTTL(*mappings_iter)) ||
135 (mappings_iter->status == AccountMapping::MAPPED &&
136 CanTriggerUpdate(mappings_iter->status_change_timestamp))) {
137 mappings_iter->last_message_id.clear();
138 SendAddMappingMessage(*mappings_iter);
144 void GCMAccountMapper::ShutdownHandler() {
145 initialized_ = false;
146 accounts_.clear();
147 registration_id_.clear();
148 dispatch_message_callback_.Reset();
151 void GCMAccountMapper::OnMessage(const std::string& app_id,
152 const GCMClient::IncomingMessage& message) {
153 DCHECK_EQ(app_id, kGCMAccountMapperAppId);
154 // TODO(fgorski): Report Send to Gaia ID failures using UMA.
156 if (dispatch_message_callback_.is_null()) {
157 DVLOG(1) << "dispatch_message_callback_ missing in GCMAccountMapper";
158 return;
161 GCMClient::MessageData::const_iterator it =
162 message.data.find(kGCMSendToGaiaIdAppIdKey);
163 if (it == message.data.end()) {
164 DVLOG(1) << "Send to Gaia ID failure: Embedded app ID missing.";
165 return;
168 std::string embedded_app_id = it->second;
169 if (embedded_app_id.empty()) {
170 DVLOG(1) << "Send to Gaia ID failure: Embedded app ID is empty.";
171 return;
174 // Ensuring the message does not carry the embedded app ID.
175 GCMClient::IncomingMessage new_message = message;
176 new_message.data.erase(new_message.data.find(kGCMSendToGaiaIdAppIdKey));
177 dispatch_message_callback_.Run(embedded_app_id, new_message);
180 void GCMAccountMapper::OnMessagesDeleted(const std::string& app_id) {
181 // Account message does not expect messages right now.
184 void GCMAccountMapper::OnSendError(
185 const std::string& app_id,
186 const GCMClient::SendErrorDetails& send_error_details) {
187 DCHECK_EQ(app_id, kGCMAccountMapperAppId);
189 AccountMappings::iterator account_mapping_it =
190 FindMappingByMessageId(send_error_details.message_id);
192 if (account_mapping_it == accounts_.end())
193 return;
195 if (send_error_details.result != GCMClient::TTL_EXCEEDED) {
196 DVLOG(1) << "Send error result different than TTL EXCEEDED: "
197 << send_error_details.result << ". "
198 << "Postponing the retry until a new batch of tokens arrives.";
199 return;
202 if (account_mapping_it->status == AccountMapping::REMOVING) {
203 // Another message to remove mapping can be sent immediately, because TTL
204 // for those is one day. No need to back off.
205 SendRemoveMappingMessage(*account_mapping_it);
206 } else {
207 if (account_mapping_it->status == AccountMapping::ADDING) {
208 // There is no mapping established, so we can remove the entry.
209 // Getting a fresh token will trigger a new attempt.
210 gcm_driver_->RemoveAccountMapping(account_mapping_it->account_id);
211 accounts_.erase(account_mapping_it);
212 } else {
213 // Account is already MAPPED, we have to wait for another token.
214 account_mapping_it->last_message_id.clear();
215 gcm_driver_->UpdateAccountMapping(*account_mapping_it);
220 void GCMAccountMapper::OnSendAcknowledged(const std::string& app_id,
221 const std::string& message_id) {
222 DCHECK_EQ(app_id, kGCMAccountMapperAppId);
223 AccountMappings::iterator account_mapping_it =
224 FindMappingByMessageId(message_id);
226 DVLOG(1) << "OnSendAcknowledged with message ID: " << message_id;
228 if (account_mapping_it == accounts_.end())
229 return;
231 // Here is where we advance a status of a mapping and persist or remove.
232 if (account_mapping_it->status == AccountMapping::REMOVING) {
233 // Message removing the account has been confirmed by the GCM, we can remove
234 // all the information related to the account (from memory and store).
235 gcm_driver_->RemoveAccountMapping(account_mapping_it->account_id);
236 accounts_.erase(account_mapping_it);
237 } else {
238 // Mapping status is ADDING only when it is a first time mapping.
239 DCHECK(account_mapping_it->status == AccountMapping::ADDING ||
240 account_mapping_it->status == AccountMapping::MAPPED);
242 // Account is marked as mapped with the current time.
243 account_mapping_it->status = AccountMapping::MAPPED;
244 account_mapping_it->status_change_timestamp = clock_->Now();
245 // There is no pending message for the account.
246 account_mapping_it->last_message_id.clear();
248 gcm_driver_->UpdateAccountMapping(*account_mapping_it);
252 bool GCMAccountMapper::CanHandle(const std::string& app_id) const {
253 return app_id.compare(kGCMAccountMapperAppId) == 0;
256 bool GCMAccountMapper::IsReady() {
257 return initialized_ && gcm_driver_->IsStarted() && !registration_id_.empty();
260 void GCMAccountMapper::SendAddMappingMessage(AccountMapping& account_mapping) {
261 CreateAndSendMessage(account_mapping);
264 void GCMAccountMapper::SendRemoveMappingMessage(
265 AccountMapping& account_mapping) {
266 // We want to persist an account that is being removed as quickly as possible
267 // as well as clean up the last message information.
268 if (account_mapping.status != AccountMapping::REMOVING) {
269 account_mapping.status = AccountMapping::REMOVING;
270 account_mapping.status_change_timestamp = clock_->Now();
273 account_mapping.last_message_id.clear();
275 gcm_driver_->UpdateAccountMapping(account_mapping);
277 CreateAndSendMessage(account_mapping);
280 void GCMAccountMapper::CreateAndSendMessage(
281 const AccountMapping& account_mapping) {
282 GCMClient::OutgoingMessage outgoing_message;
283 outgoing_message.id = GenerateMessageID();
284 outgoing_message.data[kRegistrationIdMessgaeKey] = registration_id_;
285 outgoing_message.data[kAccountMessageKey] = account_mapping.email;
287 if (account_mapping.status == AccountMapping::REMOVING) {
288 outgoing_message.time_to_live = kGCMRemoveMappingMessageTTL;
289 outgoing_message.data[kRemoveAccountKey] = kRemoveAccountValue;
290 } else {
291 outgoing_message.data[kTokenMessageKey] = account_mapping.access_token;
292 outgoing_message.time_to_live = kGCMAddMappingMessageTTL;
295 gcm_driver_->Send(kGCMAccountMapperAppId,
296 kGCMAccountMapperSendTo,
297 outgoing_message,
298 base::Bind(&GCMAccountMapper::OnSendFinished,
299 weak_ptr_factory_.GetWeakPtr(),
300 account_mapping.account_id));
303 void GCMAccountMapper::OnSendFinished(const std::string& account_id,
304 const std::string& message_id,
305 GCMClient::Result result) {
306 // TODO(fgorski): Add another attempt, in case the QUEUE is not full.
307 if (result != GCMClient::SUCCESS)
308 return;
310 AccountMapping* account_mapping = FindMappingByAccountId(account_id);
311 DCHECK(account_mapping);
313 // If we are dealing with account with status NEW, it is the first time
314 // mapping, and we should mark it as ADDING.
315 if (account_mapping->status == AccountMapping::NEW) {
316 account_mapping->status = AccountMapping::ADDING;
317 account_mapping->status_change_timestamp = clock_->Now();
320 account_mapping->last_message_id = message_id;
322 gcm_driver_->UpdateAccountMapping(*account_mapping);
325 void GCMAccountMapper::GetRegistration() {
326 DCHECK(registration_id_.empty());
327 std::vector<std::string> sender_ids;
328 sender_ids.push_back(kGCMAccountMapperSenderId);
329 gcm_driver_->Register(kGCMAccountMapperAppId,
330 sender_ids,
331 base::Bind(&GCMAccountMapper::OnRegisterFinished,
332 weak_ptr_factory_.GetWeakPtr()));
335 void GCMAccountMapper::OnRegisterFinished(const std::string& registration_id,
336 GCMClient::Result result) {
337 if (result == GCMClient::SUCCESS)
338 registration_id_ = registration_id;
340 if (IsReady()) {
341 if (!pending_account_tokens_.empty()) {
342 SetAccountTokens(pending_account_tokens_);
343 pending_account_tokens_.clear();
348 bool GCMAccountMapper::CanTriggerUpdate(
349 const base::Time& last_update_time) const {
350 return last_update_time +
351 base::TimeDelta::FromHours(kGCMUpdateIntervalHours -
352 kGCMUpdateEarlyStartHours) <
353 clock_->Now();
356 bool GCMAccountMapper::IsLastStatusChangeOlderThanTTL(
357 const AccountMapping& account_mapping) const {
358 int ttl_seconds = account_mapping.status == AccountMapping::REMOVING ?
359 kGCMRemoveMappingMessageTTL : kGCMAddMappingMessageTTL;
360 return account_mapping.status_change_timestamp +
361 base::TimeDelta::FromSeconds(ttl_seconds) < clock_->Now();
364 AccountMapping* GCMAccountMapper::FindMappingByAccountId(
365 const std::string& account_id) {
366 for (AccountMappings::iterator iter = accounts_.begin();
367 iter != accounts_.end();
368 ++iter) {
369 if (iter->account_id == account_id)
370 return &*iter;
373 return NULL;
376 GCMAccountMapper::AccountMappings::iterator
377 GCMAccountMapper::FindMappingByMessageId(const std::string& message_id) {
378 for (std::vector<AccountMapping>::iterator iter = accounts_.begin();
379 iter != accounts_.end();
380 ++iter) {
381 if (iter->last_message_id == message_id)
382 return iter;
385 return accounts_.end();
388 void GCMAccountMapper::SetClockForTesting(scoped_ptr<base::Clock> clock) {
389 clock_ = clock.Pass();
392 } // namespace gcm