gcm_account_mapper.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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