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"
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"
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();
42 const char kGCMAccountMapperAppId
[] = "com.google.android.gms";
44 GCMAccountMapper::GCMAccountMapper(GCMDriver
* gcm_driver
)
45 : gcm_driver_(gcm_driver
),
46 clock_(new base::DefaultClock
),
48 weak_ptr_factory_(this) {
51 GCMAccountMapper::~GCMAccountMapper() {
54 void GCMAccountMapper::Initialize(const AccountMappings
& account_mappings
,
55 const DispatchMessageCallback
& callback
) {
56 DCHECK(!initialized_
);
58 accounts_
= account_mappings
;
59 dispatch_message_callback_
= callback
;
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.
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())
79 // Start from removing the old tokens, from all of the known accounts.
80 for (AccountMappings::iterator iter
= accounts_
.begin();
81 iter
!= accounts_
.end();
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();
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
);
101 // Since we got a token for an account, drop the remove message and treat
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
116 for (AccountMappings::iterator mappings_iter
= accounts_
.begin();
117 mappings_iter
!= accounts_
.end();
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
);
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;
147 registration_id_
.clear();
148 dispatch_message_callback_
.Reset();
151 void GCMAccountMapper::OnMessage(const std::string
& app_id
,
152 const 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";
161 MessageData::const_iterator it
= message
.data
.find(kGCMSendToGaiaIdAppIdKey
);
162 if (it
== message
.data
.end()) {
163 DVLOG(1) << "Send to Gaia ID failure: Embedded app ID missing.";
167 std::string embedded_app_id
= it
->second
;
168 if (embedded_app_id
.empty()) {
169 DVLOG(1) << "Send to Gaia ID failure: Embedded app ID is empty.";
173 // Ensuring the message does not carry the embedded app ID.
174 IncomingMessage new_message
= message
;
175 new_message
.data
.erase(new_message
.data
.find(kGCMSendToGaiaIdAppIdKey
));
176 dispatch_message_callback_
.Run(embedded_app_id
, new_message
);
179 void GCMAccountMapper::OnMessagesDeleted(const std::string
& app_id
) {
180 // Account message does not expect messages right now.
183 void GCMAccountMapper::OnSendError(
184 const std::string
& app_id
,
185 const GCMClient::SendErrorDetails
& send_error_details
) {
186 DCHECK_EQ(app_id
, kGCMAccountMapperAppId
);
188 AccountMappings::iterator account_mapping_it
=
189 FindMappingByMessageId(send_error_details
.message_id
);
191 if (account_mapping_it
== accounts_
.end())
194 if (send_error_details
.result
!= GCMClient::TTL_EXCEEDED
) {
195 DVLOG(1) << "Send error result different than TTL EXCEEDED: "
196 << send_error_details
.result
<< ". "
197 << "Postponing the retry until a new batch of tokens arrives.";
201 if (account_mapping_it
->status
== AccountMapping::REMOVING
) {
202 // Another message to remove mapping can be sent immediately, because TTL
203 // for those is one day. No need to back off.
204 SendRemoveMappingMessage(*account_mapping_it
);
206 if (account_mapping_it
->status
== AccountMapping::ADDING
) {
207 // There is no mapping established, so we can remove the entry.
208 // Getting a fresh token will trigger a new attempt.
209 gcm_driver_
->RemoveAccountMapping(account_mapping_it
->account_id
);
210 accounts_
.erase(account_mapping_it
);
212 // Account is already MAPPED, we have to wait for another token.
213 account_mapping_it
->last_message_id
.clear();
214 gcm_driver_
->UpdateAccountMapping(*account_mapping_it
);
219 void GCMAccountMapper::OnSendAcknowledged(const std::string
& app_id
,
220 const std::string
& message_id
) {
221 DCHECK_EQ(app_id
, kGCMAccountMapperAppId
);
222 AccountMappings::iterator account_mapping_it
=
223 FindMappingByMessageId(message_id
);
225 DVLOG(1) << "OnSendAcknowledged with message ID: " << message_id
;
227 if (account_mapping_it
== accounts_
.end())
230 // Here is where we advance a status of a mapping and persist or remove.
231 if (account_mapping_it
->status
== AccountMapping::REMOVING
) {
232 // Message removing the account has been confirmed by the GCM, we can remove
233 // all the information related to the account (from memory and store).
234 gcm_driver_
->RemoveAccountMapping(account_mapping_it
->account_id
);
235 accounts_
.erase(account_mapping_it
);
237 // Mapping status is ADDING only when it is a first time mapping.
238 DCHECK(account_mapping_it
->status
== AccountMapping::ADDING
||
239 account_mapping_it
->status
== AccountMapping::MAPPED
);
241 // Account is marked as mapped with the current time.
242 account_mapping_it
->status
= AccountMapping::MAPPED
;
243 account_mapping_it
->status_change_timestamp
= clock_
->Now();
244 // There is no pending message for the account.
245 account_mapping_it
->last_message_id
.clear();
247 gcm_driver_
->UpdateAccountMapping(*account_mapping_it
);
251 bool GCMAccountMapper::CanHandle(const std::string
& app_id
) const {
252 return app_id
.compare(kGCMAccountMapperAppId
) == 0;
255 bool GCMAccountMapper::IsReady() {
256 return initialized_
&& gcm_driver_
->IsStarted() && !registration_id_
.empty();
259 void GCMAccountMapper::SendAddMappingMessage(AccountMapping
& account_mapping
) {
260 CreateAndSendMessage(account_mapping
);
263 void GCMAccountMapper::SendRemoveMappingMessage(
264 AccountMapping
& account_mapping
) {
265 // We want to persist an account that is being removed as quickly as possible
266 // as well as clean up the last message information.
267 if (account_mapping
.status
!= AccountMapping::REMOVING
) {
268 account_mapping
.status
= AccountMapping::REMOVING
;
269 account_mapping
.status_change_timestamp
= clock_
->Now();
272 account_mapping
.last_message_id
.clear();
274 gcm_driver_
->UpdateAccountMapping(account_mapping
);
276 CreateAndSendMessage(account_mapping
);
279 void GCMAccountMapper::CreateAndSendMessage(
280 const AccountMapping
& account_mapping
) {
281 OutgoingMessage outgoing_message
;
282 outgoing_message
.id
= GenerateMessageID();
283 outgoing_message
.data
[kRegistrationIdMessgaeKey
] = registration_id_
;
284 outgoing_message
.data
[kAccountMessageKey
] = account_mapping
.email
;
286 if (account_mapping
.status
== AccountMapping::REMOVING
) {
287 outgoing_message
.time_to_live
= kGCMRemoveMappingMessageTTL
;
288 outgoing_message
.data
[kRemoveAccountKey
] = kRemoveAccountValue
;
290 outgoing_message
.data
[kTokenMessageKey
] = account_mapping
.access_token
;
291 outgoing_message
.time_to_live
= kGCMAddMappingMessageTTL
;
294 gcm_driver_
->Send(kGCMAccountMapperAppId
,
295 kGCMAccountMapperSendTo
,
297 base::Bind(&GCMAccountMapper::OnSendFinished
,
298 weak_ptr_factory_
.GetWeakPtr(),
299 account_mapping
.account_id
));
302 void GCMAccountMapper::OnSendFinished(const std::string
& account_id
,
303 const std::string
& message_id
,
304 GCMClient::Result result
) {
305 // TODO(fgorski): Add another attempt, in case the QUEUE is not full.
306 if (result
!= GCMClient::SUCCESS
)
309 AccountMapping
* account_mapping
= FindMappingByAccountId(account_id
);
310 DCHECK(account_mapping
);
312 // If we are dealing with account with status NEW, it is the first time
313 // mapping, and we should mark it as ADDING.
314 if (account_mapping
->status
== AccountMapping::NEW
) {
315 account_mapping
->status
= AccountMapping::ADDING
;
316 account_mapping
->status_change_timestamp
= clock_
->Now();
319 account_mapping
->last_message_id
= message_id
;
321 gcm_driver_
->UpdateAccountMapping(*account_mapping
);
324 void GCMAccountMapper::GetRegistration() {
325 DCHECK(registration_id_
.empty());
326 std::vector
<std::string
> sender_ids
;
327 sender_ids
.push_back(kGCMAccountMapperSenderId
);
328 gcm_driver_
->Register(kGCMAccountMapperAppId
,
330 base::Bind(&GCMAccountMapper::OnRegisterFinished
,
331 weak_ptr_factory_
.GetWeakPtr()));
334 void GCMAccountMapper::OnRegisterFinished(const std::string
& registration_id
,
335 GCMClient::Result result
) {
336 if (result
== GCMClient::SUCCESS
)
337 registration_id_
= registration_id
;
340 if (!pending_account_tokens_
.empty()) {
341 SetAccountTokens(pending_account_tokens_
);
342 pending_account_tokens_
.clear();
347 bool GCMAccountMapper::CanTriggerUpdate(
348 const base::Time
& last_update_time
) const {
349 return last_update_time
+
350 base::TimeDelta::FromHours(kGCMUpdateIntervalHours
-
351 kGCMUpdateEarlyStartHours
) <
355 bool GCMAccountMapper::IsLastStatusChangeOlderThanTTL(
356 const AccountMapping
& account_mapping
) const {
357 int ttl_seconds
= account_mapping
.status
== AccountMapping::REMOVING
?
358 kGCMRemoveMappingMessageTTL
: kGCMAddMappingMessageTTL
;
359 return account_mapping
.status_change_timestamp
+
360 base::TimeDelta::FromSeconds(ttl_seconds
) < clock_
->Now();
363 AccountMapping
* GCMAccountMapper::FindMappingByAccountId(
364 const std::string
& account_id
) {
365 for (AccountMappings::iterator iter
= accounts_
.begin();
366 iter
!= accounts_
.end();
368 if (iter
->account_id
== account_id
)
375 GCMAccountMapper::AccountMappings::iterator
376 GCMAccountMapper::FindMappingByMessageId(const std::string
& message_id
) {
377 for (std::vector
<AccountMapping
>::iterator iter
= accounts_
.begin();
378 iter
!= accounts_
.end();
380 if (iter
->last_message_id
== message_id
)
384 return accounts_
.end();
387 void GCMAccountMapper::SetClockForTesting(scoped_ptr
<base::Clock
> clock
) {
388 clock_
= clock
.Pass();