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