gcm_client_impl.cc revision 03b57e008b61dfcb1fbad3aea950ae0e001748b0
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_client_impl.h"
6
7#include "base/bind.h"
8#include "base/files/file_path.h"
9#include "base/logging.h"
10#include "base/memory/scoped_ptr.h"
11#include "base/message_loop/message_loop.h"
12#include "base/metrics/histogram.h"
13#include "base/sequenced_task_runner.h"
14#include "base/strings/string_number_conversions.h"
15#include "base/strings/stringprintf.h"
16#include "base/time/default_clock.h"
17#include "google_apis/gcm/base/encryptor.h"
18#include "google_apis/gcm/base/mcs_message.h"
19#include "google_apis/gcm/base/mcs_util.h"
20#include "google_apis/gcm/engine/checkin_request.h"
21#include "google_apis/gcm/engine/connection_factory_impl.h"
22#include "google_apis/gcm/engine/gcm_store_impl.h"
23#include "google_apis/gcm/monitoring/gcm_stats_recorder.h"
24#include "google_apis/gcm/protocol/checkin.pb.h"
25#include "google_apis/gcm/protocol/mcs.pb.h"
26#include "net/http/http_network_session.h"
27#include "net/http/http_transaction_factory.h"
28#include "net/url_request/url_request_context.h"
29#include "url/gurl.h"
30
31namespace gcm {
32
33namespace {
34
35// Backoff policy. Shared across reconnection logic and checkin/(un)registration
36// retries.
37// Note: In order to ensure a minimum of 20 seconds between server errors (for
38// server reasons), we have a 30s +- 10s (33%) jitter initial backoff.
39// TODO(zea): consider sharing/synchronizing the scheduling of backoff retries
40// themselves.
41const net::BackoffEntry::Policy kDefaultBackoffPolicy = {
42  // Number of initial errors (in sequence) to ignore before applying
43  // exponential back-off rules.
44  0,
45
46  // Initial delay for exponential back-off in ms.
47  30 * 1000,  // 30 seconds.
48
49  // Factor by which the waiting time will be multiplied.
50  2,
51
52  // Fuzzing percentage. ex: 10% will spread requests randomly
53  // between 90%-100% of the calculated time.
54  0.33,  // 33%.
55
56  // Maximum amount of time we are willing to delay our request in ms.
57  10 * 60 * 1000, // 10 minutes.
58
59  // Time to keep an entry from being discarded even when it
60  // has no significant state, -1 to never discard.
61  -1,
62
63  // Don't use initial delay unless the last request was an error.
64  false,
65};
66
67// Indicates a message type of the received message.
68enum MessageType {
69  UNKNOWN,           // Undetermined type.
70  DATA_MESSAGE,      // Regular data message.
71  DELETED_MESSAGES,  // Messages were deleted on the server.
72  SEND_ERROR,        // Error sending a message.
73};
74
75enum OutgoingMessageTTLCategory {
76  TTL_ZERO,
77  TTL_LESS_THAN_OR_EQUAL_TO_ONE_MINUTE,
78  TTL_LESS_THAN_OR_EQUAL_TO_ONE_HOUR,
79  TTL_LESS_THAN_OR_EQUAL_TO_ONE_DAY,
80  TTL_LESS_THAN_OR_EQUAL_TO_ONE_WEEK,
81  TTL_MORE_THAN_ONE_WEEK,
82  TTL_MAXIMUM,
83  // NOTE: always keep this entry at the end. Add new TTL category only
84  // immediately above this line. Make sure to update the corresponding
85  // histogram enum accordingly.
86  TTL_CATEGORY_COUNT
87};
88
89const int kMaxRegistrationRetries = 5;
90const char kMessageTypeDataMessage[] = "gcm";
91const char kMessageTypeDeletedMessagesKey[] = "deleted_messages";
92const char kMessageTypeKey[] = "message_type";
93const char kMessageTypeSendErrorKey[] = "send_error";
94const char kSendErrorMessageIdKey[] = "google.message_id";
95const char kSendMessageFromValue[] = "gcm@chrome.com";
96const int64 kDefaultUserSerialNumber = 0LL;
97
98GCMClient::Result ToGCMClientResult(MCSClient::MessageSendStatus status) {
99  switch (status) {
100    case MCSClient::QUEUED:
101      return GCMClient::SUCCESS;
102    case MCSClient::QUEUE_SIZE_LIMIT_REACHED:
103      return GCMClient::NETWORK_ERROR;
104    case MCSClient::APP_QUEUE_SIZE_LIMIT_REACHED:
105      return GCMClient::NETWORK_ERROR;
106    case MCSClient::MESSAGE_TOO_LARGE:
107      return GCMClient::INVALID_PARAMETER;
108    case MCSClient::NO_CONNECTION_ON_ZERO_TTL:
109      return GCMClient::NETWORK_ERROR;
110    case MCSClient::TTL_EXCEEDED:
111      return GCMClient::NETWORK_ERROR;
112    case MCSClient::SENT:
113    default:
114      NOTREACHED();
115      break;
116  }
117  return GCMClientImpl::UNKNOWN_ERROR;
118}
119
120void ToCheckinProtoVersion(
121    const GCMClient::ChromeBuildInfo& chrome_build_info,
122    checkin_proto::ChromeBuildProto* android_build_info) {
123  checkin_proto::ChromeBuildProto_Platform platform;
124  switch (chrome_build_info.platform) {
125    case GCMClient::PLATFORM_WIN:
126      platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_WIN;
127      break;
128    case GCMClient::PLATFORM_MAC:
129      platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_MAC;
130      break;
131    case GCMClient::PLATFORM_LINUX:
132      platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_LINUX;
133      break;
134    case GCMClient::PLATFORM_IOS:
135      platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_IOS;
136      break;
137    case GCMClient::PLATFORM_ANDROID:
138      platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_ANDROID;
139      break;
140    case GCMClient::PLATFORM_CROS:
141      platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_CROS;
142      break;
143    case GCMClient::PLATFORM_UNKNOWN:
144      // For unknown platform, return as LINUX.
145      platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_LINUX;
146      break;
147    default:
148      NOTREACHED();
149      platform = checkin_proto::ChromeBuildProto_Platform_PLATFORM_LINUX;
150      break;
151  }
152  android_build_info->set_platform(platform);
153
154  checkin_proto::ChromeBuildProto_Channel channel;
155  switch (chrome_build_info.channel) {
156    case GCMClient::CHANNEL_STABLE:
157      channel = checkin_proto::ChromeBuildProto_Channel_CHANNEL_STABLE;
158      break;
159    case GCMClient::CHANNEL_BETA:
160      channel = checkin_proto::ChromeBuildProto_Channel_CHANNEL_BETA;
161      break;
162    case GCMClient::CHANNEL_DEV:
163      channel = checkin_proto::ChromeBuildProto_Channel_CHANNEL_DEV;
164      break;
165    case GCMClient::CHANNEL_CANARY:
166      channel = checkin_proto::ChromeBuildProto_Channel_CHANNEL_CANARY;
167      break;
168    case GCMClient::CHANNEL_UNKNOWN:
169      channel = checkin_proto::ChromeBuildProto_Channel_CHANNEL_UNKNOWN;
170      break;
171    default:
172      NOTREACHED();
173      channel = checkin_proto::ChromeBuildProto_Channel_CHANNEL_UNKNOWN;
174      break;
175  }
176  android_build_info->set_channel(channel);
177
178  android_build_info->set_chrome_version(chrome_build_info.version);
179}
180
181MessageType DecodeMessageType(const std::string& value) {
182  if (kMessageTypeDeletedMessagesKey == value)
183    return DELETED_MESSAGES;
184  if (kMessageTypeSendErrorKey == value)
185    return SEND_ERROR;
186  if (kMessageTypeDataMessage == value)
187    return DATA_MESSAGE;
188  return UNKNOWN;
189}
190
191void RecordOutgoingMessageToUMA(
192    const gcm::GCMClient::OutgoingMessage& message) {
193  OutgoingMessageTTLCategory ttl_category;
194  if (message.time_to_live == 0)
195    ttl_category = TTL_ZERO;
196  else if (message.time_to_live <= 60 )
197    ttl_category = TTL_LESS_THAN_OR_EQUAL_TO_ONE_MINUTE;
198  else if (message.time_to_live <= 60 * 60)
199    ttl_category = TTL_LESS_THAN_OR_EQUAL_TO_ONE_HOUR;
200  else if (message.time_to_live <= 24 * 60 * 60)
201    ttl_category = TTL_LESS_THAN_OR_EQUAL_TO_ONE_DAY;
202  else if (message.time_to_live <= 7 * 24 * 60 * 60)
203    ttl_category = TTL_LESS_THAN_OR_EQUAL_TO_ONE_WEEK;
204  else if (message.time_to_live < gcm::GCMClient::OutgoingMessage::kMaximumTTL)
205    ttl_category = TTL_MORE_THAN_ONE_WEEK;
206  else
207    ttl_category = TTL_MAXIMUM;
208
209  UMA_HISTOGRAM_ENUMERATION("GCM.GCMOutgoingMessageTTLCategory",
210                            ttl_category,
211                            TTL_CATEGORY_COUNT);
212}
213
214}  // namespace
215
216GCMInternalsBuilder::GCMInternalsBuilder() {}
217GCMInternalsBuilder::~GCMInternalsBuilder() {}
218
219scoped_ptr<base::Clock> GCMInternalsBuilder::BuildClock() {
220  return make_scoped_ptr<base::Clock>(new base::DefaultClock());
221}
222
223scoped_ptr<MCSClient> GCMInternalsBuilder::BuildMCSClient(
224    const std::string& version,
225    base::Clock* clock,
226    ConnectionFactory* connection_factory,
227    GCMStore* gcm_store,
228    GCMStatsRecorder* recorder) {
229  return make_scoped_ptr<MCSClient>(
230      new MCSClient(version,
231                    clock,
232                    connection_factory,
233                    gcm_store,
234                    recorder));
235}
236
237scoped_ptr<ConnectionFactory> GCMInternalsBuilder::BuildConnectionFactory(
238      const std::vector<GURL>& endpoints,
239      const net::BackoffEntry::Policy& backoff_policy,
240      const scoped_refptr<net::HttpNetworkSession>& gcm_network_session,
241      const scoped_refptr<net::HttpNetworkSession>& http_network_session,
242      net::NetLog* net_log,
243      GCMStatsRecorder* recorder) {
244  return make_scoped_ptr<ConnectionFactory>(
245      new ConnectionFactoryImpl(endpoints,
246                                backoff_policy,
247                                gcm_network_session,
248                                http_network_session,
249                                net_log,
250                                recorder));
251}
252
253GCMClientImpl::CheckinInfo::CheckinInfo()
254    : android_id(0), secret(0), accounts_set(false) {
255}
256
257GCMClientImpl::CheckinInfo::~CheckinInfo() {
258}
259
260void GCMClientImpl::CheckinInfo::SnapshotCheckinAccounts() {
261  last_checkin_accounts.clear();
262  for (std::map<std::string, std::string>::iterator iter =
263           account_tokens.begin();
264       iter != account_tokens.end();
265       ++iter) {
266    last_checkin_accounts.insert(iter->first);
267  }
268}
269
270void GCMClientImpl::CheckinInfo::Reset() {
271  android_id = 0;
272  secret = 0;
273  accounts_set = false;
274  account_tokens.clear();
275  last_checkin_accounts.clear();
276}
277
278GCMClientImpl::GCMClientImpl(scoped_ptr<GCMInternalsBuilder> internals_builder)
279    : internals_builder_(internals_builder.Pass()),
280      state_(UNINITIALIZED),
281      delegate_(NULL),
282      clock_(internals_builder_->BuildClock()),
283      url_request_context_getter_(NULL),
284      pending_registration_requests_deleter_(&pending_registration_requests_),
285      pending_unregistration_requests_deleter_(
286          &pending_unregistration_requests_),
287      periodic_checkin_ptr_factory_(this),
288      weak_ptr_factory_(this) {
289}
290
291GCMClientImpl::~GCMClientImpl() {
292}
293
294void GCMClientImpl::Initialize(
295    const ChromeBuildInfo& chrome_build_info,
296    const base::FilePath& path,
297    const scoped_refptr<base::SequencedTaskRunner>& blocking_task_runner,
298    const scoped_refptr<net::URLRequestContextGetter>&
299        url_request_context_getter,
300    scoped_ptr<Encryptor> encryptor,
301    GCMClient::Delegate* delegate) {
302  DCHECK_EQ(UNINITIALIZED, state_);
303  DCHECK(url_request_context_getter);
304  DCHECK(delegate);
305
306  url_request_context_getter_ = url_request_context_getter;
307  const net::HttpNetworkSession::Params* network_session_params =
308      url_request_context_getter_->GetURLRequestContext()->
309          GetNetworkSessionParams();
310  DCHECK(network_session_params);
311  network_session_ = new net::HttpNetworkSession(*network_session_params);
312
313  chrome_build_info_ = chrome_build_info;
314
315  gcm_store_.reset(
316      new GCMStoreImpl(path, blocking_task_runner, encryptor.Pass()));
317
318  delegate_ = delegate;
319
320  recorder_.SetDelegate(this);
321
322  state_ = INITIALIZED;
323}
324
325void GCMClientImpl::Start() {
326  DCHECK_EQ(INITIALIZED, state_);
327
328  // Once the loading is completed, the check-in will be initiated.
329  gcm_store_->Load(base::Bind(&GCMClientImpl::OnLoadCompleted,
330                              weak_ptr_factory_.GetWeakPtr()));
331  state_ = LOADING;
332}
333
334void GCMClientImpl::OnLoadCompleted(scoped_ptr<GCMStore::LoadResult> result) {
335  DCHECK_EQ(LOADING, state_);
336
337  if (!result->success) {
338    ResetState();
339    return;
340  }
341
342  registrations_ = result->registrations;
343  device_checkin_info_.android_id = result->device_android_id;
344  device_checkin_info_.secret = result->device_security_token;
345  device_checkin_info_.last_checkin_accounts = result->last_checkin_accounts;
346  // A case where there were previously no accounts reported with checkin is
347  // considered to be the same as when the list of accounts is empty. It enables
348  // scheduling a periodic checkin for devices with no signed in users
349  // immediately after restart, while keeping |accounts_set == false| delays the
350  // checkin until the list of accounts is set explicitly.
351  if (result->last_checkin_accounts.size() == 0)
352    device_checkin_info_.accounts_set = true;
353  last_checkin_time_ = result->last_checkin_time;
354  gservices_settings_.UpdateFromLoadResult(*result);
355  InitializeMCSClient(result.Pass());
356
357  if (device_checkin_info_.IsValid()) {
358    SchedulePeriodicCheckin();
359    OnReady();
360    return;
361  }
362
363  state_ = INITIAL_DEVICE_CHECKIN;
364  device_checkin_info_.Reset();
365  StartCheckin();
366}
367
368void GCMClientImpl::InitializeMCSClient(
369    scoped_ptr<GCMStore::LoadResult> result) {
370  std::vector<GURL> endpoints;
371  endpoints.push_back(gservices_settings_.GetMCSMainEndpoint());
372  endpoints.push_back(gservices_settings_.GetMCSFallbackEndpoint());
373  connection_factory_ = internals_builder_->BuildConnectionFactory(
374      endpoints,
375      kDefaultBackoffPolicy,
376      network_session_,
377      url_request_context_getter_->GetURLRequestContext()
378          ->http_transaction_factory()
379          ->GetSession(),
380      net_log_.net_log(),
381      &recorder_);
382  connection_factory_->SetConnectionListener(this);
383  mcs_client_ = internals_builder_->BuildMCSClient(
384      chrome_build_info_.version,
385      clock_.get(),
386      connection_factory_.get(),
387      gcm_store_.get(),
388      &recorder_).Pass();
389
390  mcs_client_->Initialize(
391      base::Bind(&GCMClientImpl::OnMCSError, weak_ptr_factory_.GetWeakPtr()),
392      base::Bind(&GCMClientImpl::OnMessageReceivedFromMCS,
393                 weak_ptr_factory_.GetWeakPtr()),
394      base::Bind(&GCMClientImpl::OnMessageSentToMCS,
395                 weak_ptr_factory_.GetWeakPtr()),
396      result.Pass());
397}
398
399void GCMClientImpl::OnFirstTimeDeviceCheckinCompleted(
400    const CheckinInfo& checkin_info) {
401  DCHECK(!device_checkin_info_.IsValid());
402
403  device_checkin_info_.android_id = checkin_info.android_id;
404  device_checkin_info_.secret = checkin_info.secret;
405  // If accounts were not set by now, we can consider them set (to empty list)
406  // to make sure periodic checkins get scheduled after initial checkin.
407  device_checkin_info_.accounts_set = true;
408  gcm_store_->SetDeviceCredentials(
409      checkin_info.android_id, checkin_info.secret,
410      base::Bind(&GCMClientImpl::SetDeviceCredentialsCallback,
411                 weak_ptr_factory_.GetWeakPtr()));
412
413  OnReady();
414}
415
416void GCMClientImpl::OnReady() {
417  state_ = READY;
418  StartMCSLogin();
419
420  delegate_->OnGCMReady();
421}
422
423void GCMClientImpl::StartMCSLogin() {
424  DCHECK_EQ(READY, state_);
425  DCHECK(device_checkin_info_.IsValid());
426  mcs_client_->Login(device_checkin_info_.android_id,
427                     device_checkin_info_.secret);
428}
429
430void GCMClientImpl::ResetState() {
431  state_ = UNINITIALIZED;
432  // TODO(fgorski): reset all of the necessart objects and start over.
433}
434
435void GCMClientImpl::SetAccountsForCheckin(
436    const std::map<std::string, std::string>& account_tokens) {
437  bool accounts_set_before = device_checkin_info_.accounts_set;
438  device_checkin_info_.account_tokens = account_tokens;
439  device_checkin_info_.accounts_set = true;
440
441  DVLOG(1) << "Set account called with: " << account_tokens.size()
442           << " accounts.";
443
444  if (state_ != READY && state_ != INITIAL_DEVICE_CHECKIN)
445    return;
446
447  bool account_removed = false;
448  for (std::set<std::string>::iterator iter =
449           device_checkin_info_.last_checkin_accounts.begin();
450       iter != device_checkin_info_.last_checkin_accounts.end();
451       ++iter) {
452    if (account_tokens.find(*iter) == account_tokens.end())
453      account_removed = true;
454  }
455
456  // Checkin will be forced when any of the accounts was removed during the
457  // current Chrome session or if there has been an account removed between the
458  // restarts of Chrome. If there is a checkin in progress, it will be canceled.
459  // We only force checkin when user signs out. When there is a new account
460  // signed in, the periodic checkin will take care of adding the association in
461  // reasonable time.
462  if (account_removed) {
463    DVLOG(1) << "Detected that account has been removed. Forcing checkin.";
464    checkin_request_.reset();
465    StartCheckin();
466  } else if (!accounts_set_before) {
467    SchedulePeriodicCheckin();
468    DVLOG(1) << "Accounts set for the first time. Scheduled periodic checkin.";
469  }
470}
471
472void GCMClientImpl::UpdateAccountMapping(
473    const AccountMapping& account_mapping) {
474  gcm_store_->AddAccountMapping(account_mapping,
475                                base::Bind(&GCMClientImpl::DefaultStoreCallback,
476                                           weak_ptr_factory_.GetWeakPtr()));
477}
478
479void GCMClientImpl::RemoveAccountMapping(const std::string& account_id) {
480  gcm_store_->RemoveAccountMapping(
481      account_id,
482      base::Bind(&GCMClientImpl::DefaultStoreCallback,
483                 weak_ptr_factory_.GetWeakPtr()));
484}
485
486void GCMClientImpl::StartCheckin() {
487  // Make sure no checkin is in progress.
488  if (checkin_request_.get())
489    return;
490
491  checkin_proto::ChromeBuildProto chrome_build_proto;
492  ToCheckinProtoVersion(chrome_build_info_, &chrome_build_proto);
493  CheckinRequest::RequestInfo request_info(device_checkin_info_.android_id,
494                                           device_checkin_info_.secret,
495                                           device_checkin_info_.account_tokens,
496                                           gservices_settings_.digest(),
497                                           chrome_build_proto);
498  checkin_request_.reset(
499      new CheckinRequest(gservices_settings_.GetCheckinURL(),
500                         request_info,
501                         kDefaultBackoffPolicy,
502                         base::Bind(&GCMClientImpl::OnCheckinCompleted,
503                                    weak_ptr_factory_.GetWeakPtr()),
504                         url_request_context_getter_,
505                         &recorder_));
506  // Taking a snapshot of the accounts count here, as there might be an asynch
507  // update of the account tokens while checkin is in progress.
508  device_checkin_info_.SnapshotCheckinAccounts();
509  checkin_request_->Start();
510}
511
512void GCMClientImpl::OnCheckinCompleted(
513    const checkin_proto::AndroidCheckinResponse& checkin_response) {
514  checkin_request_.reset();
515
516  if (!checkin_response.has_android_id() ||
517      !checkin_response.has_security_token()) {
518    // TODO(fgorski): I don't think a retry here will help, we should probably
519    // start over. By checking in with (0, 0).
520    return;
521  }
522
523  CheckinInfo checkin_info;
524  checkin_info.android_id = checkin_response.android_id();
525  checkin_info.secret = checkin_response.security_token();
526
527  if (state_ == INITIAL_DEVICE_CHECKIN) {
528    OnFirstTimeDeviceCheckinCompleted(checkin_info);
529  } else {
530    // checkin_info is not expected to change after a periodic checkin as it
531    // would invalidate the registratoin IDs.
532    DCHECK_EQ(READY, state_);
533    DCHECK_EQ(device_checkin_info_.android_id, checkin_info.android_id);
534    DCHECK_EQ(device_checkin_info_.secret, checkin_info.secret);
535  }
536
537  if (device_checkin_info_.IsValid()) {
538    // First update G-services settings, as something might have changed.
539    if (gservices_settings_.UpdateFromCheckinResponse(checkin_response)) {
540      gcm_store_->SetGServicesSettings(
541          gservices_settings_.settings_map(),
542          gservices_settings_.digest(),
543          base::Bind(&GCMClientImpl::SetGServicesSettingsCallback,
544                     weak_ptr_factory_.GetWeakPtr()));
545    }
546
547    last_checkin_time_ = clock_->Now();
548    gcm_store_->SetLastCheckinInfo(
549        last_checkin_time_,
550        device_checkin_info_.last_checkin_accounts,
551        base::Bind(&GCMClientImpl::SetLastCheckinInfoCallback,
552                   weak_ptr_factory_.GetWeakPtr()));
553    SchedulePeriodicCheckin();
554  }
555}
556
557void GCMClientImpl::SetGServicesSettingsCallback(bool success) {
558  DCHECK(success);
559}
560
561void GCMClientImpl::SchedulePeriodicCheckin() {
562  // Make sure no checkin is in progress.
563  if (checkin_request_.get() || !device_checkin_info_.accounts_set)
564    return;
565
566  // There should be only one periodic checkin pending at a time. Removing
567  // pending periodic checkin to schedule a new one.
568  periodic_checkin_ptr_factory_.InvalidateWeakPtrs();
569
570  base::TimeDelta time_to_next_checkin = GetTimeToNextCheckin();
571  if (time_to_next_checkin < base::TimeDelta())
572    time_to_next_checkin = base::TimeDelta();
573
574  base::MessageLoop::current()->PostDelayedTask(
575      FROM_HERE,
576      base::Bind(&GCMClientImpl::StartCheckin,
577                 periodic_checkin_ptr_factory_.GetWeakPtr()),
578      time_to_next_checkin);
579}
580
581base::TimeDelta GCMClientImpl::GetTimeToNextCheckin() const {
582  return last_checkin_time_ + gservices_settings_.GetCheckinInterval() -
583         clock_->Now();
584}
585
586void GCMClientImpl::SetLastCheckinInfoCallback(bool success) {
587  // TODO(fgorski): This is one of the signals that store needs a rebuild.
588  DCHECK(success);
589}
590
591void GCMClientImpl::SetDeviceCredentialsCallback(bool success) {
592  // TODO(fgorski): This is one of the signals that store needs a rebuild.
593  DCHECK(success);
594}
595
596void GCMClientImpl::UpdateRegistrationCallback(bool success) {
597  // TODO(fgorski): This is one of the signals that store needs a rebuild.
598  DCHECK(success);
599}
600
601void GCMClientImpl::DefaultStoreCallback(bool success) {
602  DCHECK(success);
603}
604
605void GCMClientImpl::Stop() {
606  weak_ptr_factory_.InvalidateWeakPtrs();
607  device_checkin_info_.Reset();
608  connection_factory_.reset();
609  delegate_->OnDisconnected();
610  mcs_client_.reset();
611  checkin_request_.reset();
612  pending_registration_requests_.clear();
613  state_ = INITIALIZED;
614  gcm_store_->Close();
615}
616
617void GCMClientImpl::CheckOut() {
618  Stop();
619  gcm_store_->Destroy(base::Bind(&GCMClientImpl::OnGCMStoreDestroyed,
620                                 weak_ptr_factory_.GetWeakPtr()));
621}
622
623void GCMClientImpl::Register(const std::string& app_id,
624                             const std::vector<std::string>& sender_ids) {
625  DCHECK_EQ(state_, READY);
626
627  // If the same sender ids is provided, return the cached registration ID
628  // directly.
629  RegistrationInfoMap::const_iterator registrations_iter =
630      registrations_.find(app_id);
631  if (registrations_iter != registrations_.end() &&
632      registrations_iter->second->sender_ids == sender_ids) {
633    delegate_->OnRegisterFinished(
634        app_id, registrations_iter->second->registration_id, SUCCESS);
635    return;
636  }
637
638  RegistrationRequest::RequestInfo request_info(
639      device_checkin_info_.android_id,
640      device_checkin_info_.secret,
641      app_id,
642      sender_ids);
643  DCHECK_EQ(0u, pending_registration_requests_.count(app_id));
644
645  RegistrationRequest* registration_request =
646      new RegistrationRequest(gservices_settings_.GetRegistrationURL(),
647                              request_info,
648                              kDefaultBackoffPolicy,
649                              base::Bind(&GCMClientImpl::OnRegisterCompleted,
650                                         weak_ptr_factory_.GetWeakPtr(),
651                                         app_id,
652                                         sender_ids),
653                              kMaxRegistrationRetries,
654                              url_request_context_getter_,
655                              &recorder_);
656  pending_registration_requests_[app_id] = registration_request;
657  registration_request->Start();
658}
659
660void GCMClientImpl::OnRegisterCompleted(
661    const std::string& app_id,
662    const std::vector<std::string>& sender_ids,
663    RegistrationRequest::Status status,
664    const std::string& registration_id) {
665  DCHECK(delegate_);
666
667  Result result;
668  PendingRegistrationRequests::iterator iter =
669      pending_registration_requests_.find(app_id);
670  if (iter == pending_registration_requests_.end())
671    result = UNKNOWN_ERROR;
672  else if (status == RegistrationRequest::INVALID_SENDER)
673    result = INVALID_PARAMETER;
674  else if (registration_id.empty())
675    result = SERVER_ERROR;
676  else
677    result = SUCCESS;
678
679  if (result == SUCCESS) {
680    // Cache it.
681    linked_ptr<RegistrationInfo> registration(new RegistrationInfo);
682    registration->sender_ids = sender_ids;
683    registration->registration_id = registration_id;
684    registrations_[app_id] = registration;
685
686    // Save it in the persistent store.
687    gcm_store_->AddRegistration(
688        app_id,
689        registration,
690        base::Bind(&GCMClientImpl::UpdateRegistrationCallback,
691                   weak_ptr_factory_.GetWeakPtr()));
692  }
693
694  delegate_->OnRegisterFinished(
695      app_id, result == SUCCESS ? registration_id : std::string(), result);
696
697  if (iter != pending_registration_requests_.end()) {
698    delete iter->second;
699    pending_registration_requests_.erase(iter);
700  }
701}
702
703void GCMClientImpl::Unregister(const std::string& app_id) {
704  DCHECK_EQ(state_, READY);
705  if (pending_unregistration_requests_.count(app_id) == 1)
706    return;
707
708  // Remove from the cache and persistent store.
709  registrations_.erase(app_id);
710  gcm_store_->RemoveRegistration(
711      app_id,
712      base::Bind(&GCMClientImpl::UpdateRegistrationCallback,
713                 weak_ptr_factory_.GetWeakPtr()));
714
715  UnregistrationRequest::RequestInfo request_info(
716      device_checkin_info_.android_id,
717      device_checkin_info_.secret,
718      app_id);
719
720  UnregistrationRequest* unregistration_request = new UnregistrationRequest(
721      gservices_settings_.GetRegistrationURL(),
722      request_info,
723      kDefaultBackoffPolicy,
724      base::Bind(&GCMClientImpl::OnUnregisterCompleted,
725                 weak_ptr_factory_.GetWeakPtr(),
726                 app_id),
727      url_request_context_getter_,
728      &recorder_);
729  pending_unregistration_requests_[app_id] = unregistration_request;
730  unregistration_request->Start();
731}
732
733void GCMClientImpl::OnUnregisterCompleted(
734    const std::string& app_id,
735    UnregistrationRequest::Status status) {
736  DVLOG(1) << "Unregister completed for app: " << app_id
737           << " with " << (status ? "success." : "failure.");
738  delegate_->OnUnregisterFinished(
739      app_id,
740      status == UnregistrationRequest::SUCCESS ? SUCCESS : SERVER_ERROR);
741
742  PendingUnregistrationRequests::iterator iter =
743      pending_unregistration_requests_.find(app_id);
744  if (iter == pending_unregistration_requests_.end())
745    return;
746
747  delete iter->second;
748  pending_unregistration_requests_.erase(iter);
749}
750
751void GCMClientImpl::OnGCMStoreDestroyed(bool success) {
752  DLOG_IF(ERROR, !success) << "GCM store failed to be destroyed!";
753  UMA_HISTOGRAM_BOOLEAN("GCM.StoreDestroySucceeded", success);
754}
755
756void GCMClientImpl::Send(const std::string& app_id,
757                         const std::string& receiver_id,
758                         const OutgoingMessage& message) {
759  DCHECK_EQ(state_, READY);
760
761  RecordOutgoingMessageToUMA(message);
762
763  mcs_proto::DataMessageStanza stanza;
764  stanza.set_ttl(message.time_to_live);
765  stanza.set_sent(clock_->Now().ToInternalValue() /
766                  base::Time::kMicrosecondsPerSecond);
767  stanza.set_id(message.id);
768  stanza.set_from(kSendMessageFromValue);
769  stanza.set_to(receiver_id);
770  stanza.set_category(app_id);
771
772  for (MessageData::const_iterator iter = message.data.begin();
773       iter != message.data.end();
774       ++iter) {
775    mcs_proto::AppData* app_data = stanza.add_app_data();
776    app_data->set_key(iter->first);
777    app_data->set_value(iter->second);
778  }
779
780  MCSMessage mcs_message(stanza);
781  DVLOG(1) << "MCS message size: " << mcs_message.size();
782  mcs_client_->SendMessage(mcs_message);
783}
784
785std::string GCMClientImpl::GetStateString() const {
786  switch(state_) {
787    case GCMClientImpl::INITIALIZED:
788      return "INITIALIZED";
789    case GCMClientImpl::UNINITIALIZED:
790      return "UNINITIALIZED";
791    case GCMClientImpl::LOADING:
792      return "LOADING";
793    case GCMClientImpl::INITIAL_DEVICE_CHECKIN:
794      return "INITIAL_DEVICE_CHECKIN";
795    case GCMClientImpl::READY:
796      return "READY";
797    default:
798      NOTREACHED();
799      return std::string();
800  }
801}
802
803void GCMClientImpl::SetRecording(bool recording) {
804  recorder_.SetRecording(recording);
805}
806
807void GCMClientImpl::ClearActivityLogs() {
808  recorder_.Clear();
809}
810
811GCMClient::GCMStatistics GCMClientImpl::GetStatistics() const {
812  GCMClient::GCMStatistics stats;
813  stats.gcm_client_created = true;
814  stats.is_recording = recorder_.is_recording();
815  stats.gcm_client_state = GetStateString();
816  stats.connection_client_created = mcs_client_.get() != NULL;
817  if (connection_factory_.get())
818    stats.connection_state = connection_factory_->GetConnectionStateString();
819  if (mcs_client_.get()) {
820    stats.send_queue_size = mcs_client_->GetSendQueueSize();
821    stats.resend_queue_size = mcs_client_->GetResendQueueSize();
822  }
823  if (device_checkin_info_.android_id > 0)
824    stats.android_id = device_checkin_info_.android_id;
825  recorder_.CollectActivities(&stats.recorded_activities);
826
827  for (RegistrationInfoMap::const_iterator it = registrations_.begin();
828       it != registrations_.end(); ++it) {
829    stats.registered_app_ids.push_back(it->first);
830  }
831  return stats;
832}
833
834void GCMClientImpl::OnActivityRecorded() {
835  delegate_->OnActivityRecorded();
836}
837
838void GCMClientImpl::OnConnected(const GURL& current_server,
839                                const net::IPEndPoint& ip_endpoint) {
840  // TODO(gcm): expose current server in debug page.
841  delegate_->OnActivityRecorded();
842  delegate_->OnConnected(ip_endpoint);
843}
844
845void GCMClientImpl::OnDisconnected() {
846  delegate_->OnActivityRecorded();
847  delegate_->OnDisconnected();
848}
849
850void GCMClientImpl::OnMessageReceivedFromMCS(const gcm::MCSMessage& message) {
851  switch (message.tag()) {
852    case kLoginResponseTag:
853      DVLOG(1) << "Login response received by GCM Client. Ignoring.";
854      return;
855    case kDataMessageStanzaTag:
856      DVLOG(1) << "A downstream message received. Processing...";
857      HandleIncomingMessage(message);
858      return;
859    default:
860      NOTREACHED() << "Message with unexpected tag received by GCMClient";
861      return;
862  }
863}
864
865void GCMClientImpl::OnMessageSentToMCS(int64 user_serial_number,
866                                       const std::string& app_id,
867                                       const std::string& message_id,
868                                       MCSClient::MessageSendStatus status) {
869  DCHECK_EQ(user_serial_number, kDefaultUserSerialNumber);
870  DCHECK(delegate_);
871
872  // TTL_EXCEEDED is singled out here, because it can happen long time after the
873  // message was sent. That is why it comes as |OnMessageSendError| event rather
874  // than |OnSendFinished|. SendErrorDetails.additional_data is left empty.
875  // All other errors will be raised immediately, through asynchronous callback.
876  // It is expected that TTL_EXCEEDED will be issued for a message that was
877  // previously issued |OnSendFinished| with status SUCCESS.
878  // TODO(jianli): Consider adding UMA for this status.
879  if (status == MCSClient::TTL_EXCEEDED) {
880    SendErrorDetails send_error_details;
881    send_error_details.message_id = message_id;
882    send_error_details.result = GCMClient::TTL_EXCEEDED;
883    delegate_->OnMessageSendError(app_id, send_error_details);
884  } else if (status == MCSClient::SENT) {
885    delegate_->OnSendAcknowledged(app_id, message_id);
886  } else {
887    delegate_->OnSendFinished(app_id, message_id, ToGCMClientResult(status));
888  }
889}
890
891void GCMClientImpl::OnMCSError() {
892  // TODO(fgorski): For now it replaces the initialization method. Long term it
893  // should have an error or status passed in.
894}
895
896void GCMClientImpl::HandleIncomingMessage(const gcm::MCSMessage& message) {
897  DCHECK(delegate_);
898
899  const mcs_proto::DataMessageStanza& data_message_stanza =
900      reinterpret_cast<const mcs_proto::DataMessageStanza&>(
901          message.GetProtobuf());
902  DCHECK_EQ(data_message_stanza.device_user_id(), kDefaultUserSerialNumber);
903
904  // Copying all the data from the stanza to a MessageData object. When present,
905  // keys like kMessageTypeKey or kSendErrorMessageIdKey will be filtered out
906  // later.
907  MessageData message_data;
908  for (int i = 0; i < data_message_stanza.app_data_size(); ++i) {
909    std::string key = data_message_stanza.app_data(i).key();
910    message_data[key] = data_message_stanza.app_data(i).value();
911  }
912
913  MessageType message_type = DATA_MESSAGE;
914  MessageData::iterator iter = message_data.find(kMessageTypeKey);
915  if (iter != message_data.end()) {
916    message_type = DecodeMessageType(iter->second);
917    message_data.erase(iter);
918  }
919
920  switch (message_type) {
921    case DATA_MESSAGE:
922      HandleIncomingDataMessage(data_message_stanza, message_data);
923      break;
924    case DELETED_MESSAGES:
925      recorder_.RecordDataMessageReceived(data_message_stanza.category(),
926                                          data_message_stanza.from(),
927                                          data_message_stanza.ByteSize(),
928                                          true,
929                                          GCMStatsRecorder::DELETED_MESSAGES);
930      delegate_->OnMessagesDeleted(data_message_stanza.category());
931      break;
932    case SEND_ERROR:
933      HandleIncomingSendError(data_message_stanza, message_data);
934      break;
935    case UNKNOWN:
936    default:  // Treat default the same as UNKNOWN.
937      DVLOG(1) << "Unknown message_type received. Message ignored. "
938               << "App ID: " << data_message_stanza.category() << ".";
939      break;
940  }
941}
942
943void GCMClientImpl::HandleIncomingDataMessage(
944    const mcs_proto::DataMessageStanza& data_message_stanza,
945    MessageData& message_data) {
946  std::string app_id = data_message_stanza.category();
947
948  // Drop the message when the app is not registered for the sender of the
949  // message.
950  RegistrationInfoMap::iterator iter = registrations_.find(app_id);
951  bool not_registered =
952      iter == registrations_.end() ||
953      std::find(iter->second->sender_ids.begin(),
954                iter->second->sender_ids.end(),
955                data_message_stanza.from()) == iter->second->sender_ids.end();
956  recorder_.RecordDataMessageReceived(app_id, data_message_stanza.from(),
957      data_message_stanza.ByteSize(), !not_registered,
958      GCMStatsRecorder::DATA_MESSAGE);
959  if (not_registered) {
960    return;
961  }
962
963  IncomingMessage incoming_message;
964  incoming_message.sender_id = data_message_stanza.from();
965  if (data_message_stanza.has_token())
966    incoming_message.collapse_key = data_message_stanza.token();
967  incoming_message.data = message_data;
968  delegate_->OnMessageReceived(app_id, incoming_message);
969}
970
971void GCMClientImpl::HandleIncomingSendError(
972    const mcs_proto::DataMessageStanza& data_message_stanza,
973    MessageData& message_data) {
974  SendErrorDetails send_error_details;
975  send_error_details.additional_data = message_data;
976  send_error_details.result = SERVER_ERROR;
977
978  MessageData::iterator iter =
979      send_error_details.additional_data.find(kSendErrorMessageIdKey);
980  if (iter != send_error_details.additional_data.end()) {
981    send_error_details.message_id = iter->second;
982    send_error_details.additional_data.erase(iter);
983  }
984
985  recorder_.RecordIncomingSendError(
986      data_message_stanza.category(),
987      data_message_stanza.to(),
988      data_message_stanza.id());
989  delegate_->OnMessageSendError(data_message_stanza.category(),
990                                send_error_details);
991}
992
993}  // namespace gcm
994