1 // Copyright 2013 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 "google_apis/gcm/gcm_client_impl.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"
34 // Backoff policy. Shared across reconnection logic and checkin/(un)registration
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
40 const net::BackoffEntry::Policy kDefaultBackoffPolicy
= {
41 // Number of initial errors (in sequence) to ignore before applying
42 // exponential back-off rules.
45 // Initial delay for exponential back-off in ms.
46 30 * 1000, // 30 seconds.
48 // Factor by which the waiting time will be multiplied.
51 // Fuzzing percentage. ex: 10% will spread requests randomly
52 // between 90%-100% of the calculated time.
55 // Maximum amount of time we are willing to delay our request in ms.
56 10 * 60 * 1000, // 10 minutes.
58 // Time to keep an entry from being discarded even when it
59 // has no significant state, -1 to never discard.
62 // Don't use initial delay unless the last request was an error.
66 // Indicates a message type of the received message.
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.
74 enum OutgoingMessageTTLCategory
{
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
,
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.
88 const int kMaxRegistrationRetries
= 5;
89 const char kMessageTypeDataMessage
[] = "gcm";
90 const char kMessageTypeDeletedMessagesKey
[] = "deleted_messages";
91 const char kMessageTypeKey
[] = "message_type";
92 const char kMessageTypeSendErrorKey
[] = "send_error";
93 const char kSendErrorMessageIdKey
[] = "google.message_id";
94 const char kSendMessageFromValue
[] = "gcm@chrome.com";
95 const int64 kDefaultUserSerialNumber
= 0LL;
97 GCMClient::Result
ToGCMClientResult(MCSClient::MessageSendStatus 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
:
116 return GCMClientImpl::UNKNOWN_ERROR
;
119 void 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
;
127 case GCMClient::PLATFORM_MAC
:
128 platform
= checkin_proto::ChromeBuildProto_Platform_PLATFORM_MAC
;
130 case GCMClient::PLATFORM_LINUX
:
131 platform
= checkin_proto::ChromeBuildProto_Platform_PLATFORM_LINUX
;
133 case GCMClient::PLATFORM_IOS
:
134 platform
= checkin_proto::ChromeBuildProto_Platform_PLATFORM_IOS
;
136 case GCMClient::PLATFORM_ANDROID
:
137 platform
= checkin_proto::ChromeBuildProto_Platform_PLATFORM_ANDROID
;
139 case GCMClient::PLATFORM_CROS
:
140 platform
= checkin_proto::ChromeBuildProto_Platform_PLATFORM_CROS
;
142 case GCMClient::PLATFORM_UNKNOWN
:
143 // For unknown platform, return as LINUX.
144 platform
= checkin_proto::ChromeBuildProto_Platform_PLATFORM_LINUX
;
148 platform
= checkin_proto::ChromeBuildProto_Platform_PLATFORM_LINUX
;
151 android_build_info
->set_platform(platform
);
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
;
158 case GCMClient::CHANNEL_BETA
:
159 channel
= checkin_proto::ChromeBuildProto_Channel_CHANNEL_BETA
;
161 case GCMClient::CHANNEL_DEV
:
162 channel
= checkin_proto::ChromeBuildProto_Channel_CHANNEL_DEV
;
164 case GCMClient::CHANNEL_CANARY
:
165 channel
= checkin_proto::ChromeBuildProto_Channel_CHANNEL_CANARY
;
167 case GCMClient::CHANNEL_UNKNOWN
:
168 channel
= checkin_proto::ChromeBuildProto_Channel_CHANNEL_UNKNOWN
;
172 channel
= checkin_proto::ChromeBuildProto_Channel_CHANNEL_UNKNOWN
;
175 android_build_info
->set_channel(channel
);
177 android_build_info
->set_chrome_version(chrome_build_info
.version
);
180 MessageType
DecodeMessageType(const std::string
& value
) {
181 if (kMessageTypeDeletedMessagesKey
== value
)
182 return DELETED_MESSAGES
;
183 if (kMessageTypeSendErrorKey
== value
)
185 if (kMessageTypeDataMessage
== value
)
190 void 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
;
206 ttl_category
= TTL_MAXIMUM
;
208 UMA_HISTOGRAM_ENUMERATION("GCM.GCMOutgoingMessageTTLCategory",
215 GCMInternalsBuilder::GCMInternalsBuilder() {}
216 GCMInternalsBuilder::~GCMInternalsBuilder() {}
218 scoped_ptr
<base::Clock
> GCMInternalsBuilder::BuildClock() {
219 return make_scoped_ptr
<base::Clock
>(new base::DefaultClock());
222 scoped_ptr
<MCSClient
> GCMInternalsBuilder::BuildMCSClient(
223 const std::string
& version
,
225 ConnectionFactory
* connection_factory
,
227 GCMStatsRecorder
* recorder
) {
228 return make_scoped_ptr
<MCSClient
>(
229 new MCSClient(version
,
236 scoped_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
,
250 GCMClientImpl::GCMClientImpl(scoped_ptr
<GCMInternalsBuilder
> internals_builder
)
251 : internals_builder_(internals_builder
.Pass()),
252 state_(UNINITIALIZED
),
253 clock_(internals_builder_
->BuildClock()),
254 url_request_context_getter_(NULL
),
255 pending_registration_requests_deleter_(&pending_registration_requests_
),
256 pending_unregistration_requests_deleter_(
257 &pending_unregistration_requests_
),
258 periodic_checkin_ptr_factory_(this),
259 weak_ptr_factory_(this) {
262 GCMClientImpl::~GCMClientImpl() {
265 void GCMClientImpl::Initialize(
266 const ChromeBuildInfo
& chrome_build_info
,
267 const base::FilePath
& path
,
268 const std::vector
<std::string
>& account_ids
,
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
);
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
);
285 chrome_build_info_
= chrome_build_info
;
286 account_ids_
= account_ids
;
289 new GCMStoreImpl(path
, blocking_task_runner
, encryptor
.Pass()));
291 delegate_
= delegate
;
293 recorder_
.SetDelegate(this);
295 state_
= INITIALIZED
;
298 void GCMClientImpl::Start() {
299 DCHECK_EQ(INITIALIZED
, state_
);
301 // Once the loading is completed, the check-in will be initiated.
302 gcm_store_
->Load(base::Bind(&GCMClientImpl::OnLoadCompleted
,
303 weak_ptr_factory_
.GetWeakPtr()));
307 void GCMClientImpl::OnLoadCompleted(scoped_ptr
<GCMStore::LoadResult
> result
) {
308 DCHECK_EQ(LOADING
, state_
);
310 if (!result
->success
) {
315 registrations_
= result
->registrations
;
316 device_checkin_info_
.android_id
= result
->device_android_id
;
317 device_checkin_info_
.secret
= result
->device_security_token
;
318 last_checkin_time_
= result
->last_checkin_time
;
319 gservices_settings_
.UpdateFromLoadResult(*result
);
320 InitializeMCSClient(result
.Pass());
322 if (device_checkin_info_
.IsValid()) {
323 SchedulePeriodicCheckin();
328 state_
= INITIAL_DEVICE_CHECKIN
;
329 device_checkin_info_
.Reset();
333 void GCMClientImpl::InitializeMCSClient(
334 scoped_ptr
<GCMStore::LoadResult
> result
) {
335 std::vector
<GURL
> endpoints
;
336 endpoints
.push_back(gservices_settings_
.GetMCSMainEndpoint());
337 endpoints
.push_back(gservices_settings_
.GetMCSFallbackEndpoint());
338 connection_factory_
= internals_builder_
->BuildConnectionFactory(
340 kDefaultBackoffPolicy
,
344 mcs_client_
= internals_builder_
->BuildMCSClient(
345 chrome_build_info_
.version
,
347 connection_factory_
.get(),
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()),
360 void GCMClientImpl::OnFirstTimeDeviceCheckinCompleted(
361 const CheckinInfo
& checkin_info
) {
362 DCHECK(!device_checkin_info_
.IsValid());
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()));
374 void GCMClientImpl::OnReady() {
378 delegate_
->OnGCMReady();
381 void 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
);
388 void GCMClientImpl::ResetState() {
389 state_
= UNINITIALIZED
;
390 // TODO(fgorski): reset all of the necessart objects and start over.
393 void GCMClientImpl::StartCheckin() {
394 // Make sure no checkin is in progress.
395 if (checkin_request_
.get())
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(),
405 checkin_request_
.reset(
406 new CheckinRequest(gservices_settings_
.GetCheckinURL(),
408 kDefaultBackoffPolicy
,
409 base::Bind(&GCMClientImpl::OnCheckinCompleted
,
410 weak_ptr_factory_
.GetWeakPtr()),
411 url_request_context_getter_
,
413 checkin_request_
->Start();
416 void GCMClientImpl::OnCheckinCompleted(
417 const checkin_proto::AndroidCheckinResponse
& checkin_response
) {
418 checkin_request_
.reset();
420 if (!checkin_response
.has_android_id() ||
421 !checkin_response
.has_security_token()) {
422 // TODO(fgorski): I don't think a retry here will help, we should probably
423 // start over. By checking in with (0, 0).
427 CheckinInfo checkin_info
;
428 checkin_info
.android_id
= checkin_response
.android_id();
429 checkin_info
.secret
= checkin_response
.security_token();
431 if (state_
== INITIAL_DEVICE_CHECKIN
) {
432 OnFirstTimeDeviceCheckinCompleted(checkin_info
);
434 // checkin_info is not expected to change after a periodic checkin as it
435 // would invalidate the registratoin IDs.
436 DCHECK_EQ(READY
, state_
);
437 DCHECK_EQ(device_checkin_info_
.android_id
, checkin_info
.android_id
);
438 DCHECK_EQ(device_checkin_info_
.secret
, checkin_info
.secret
);
441 if (device_checkin_info_
.IsValid()) {
442 // First update G-services settings, as something might have changed.
443 if (gservices_settings_
.UpdateFromCheckinResponse(checkin_response
)) {
444 gcm_store_
->SetGServicesSettings(
445 gservices_settings_
.settings_map(),
446 gservices_settings_
.digest(),
447 base::Bind(&GCMClientImpl::SetGServicesSettingsCallback
,
448 weak_ptr_factory_
.GetWeakPtr()));
451 last_checkin_time_
= clock_
->Now();
452 gcm_store_
->SetLastCheckinTime(
454 base::Bind(&GCMClientImpl::SetLastCheckinTimeCallback
,
455 weak_ptr_factory_
.GetWeakPtr()));
456 SchedulePeriodicCheckin();
460 void GCMClientImpl::SetGServicesSettingsCallback(bool success
) {
464 void GCMClientImpl::SchedulePeriodicCheckin() {
465 // Make sure no checkin is in progress.
466 if (checkin_request_
.get())
469 // There should be only one periodic checkin pending at a time. Removing
470 // pending periodic checkin to schedule a new one.
471 periodic_checkin_ptr_factory_
.InvalidateWeakPtrs();
473 base::TimeDelta time_to_next_checkin
= GetTimeToNextCheckin();
474 if (time_to_next_checkin
< base::TimeDelta())
475 time_to_next_checkin
= base::TimeDelta();
477 base::MessageLoop::current()->PostDelayedTask(
479 base::Bind(&GCMClientImpl::StartCheckin
,
480 periodic_checkin_ptr_factory_
.GetWeakPtr()),
481 time_to_next_checkin
);
484 base::TimeDelta
GCMClientImpl::GetTimeToNextCheckin() const {
485 return last_checkin_time_
+ gservices_settings_
.GetCheckinInterval() -
489 void GCMClientImpl::SetLastCheckinTimeCallback(bool success
) {
490 // TODO(fgorski): This is one of the signals that store needs a rebuild.
494 void GCMClientImpl::SetDeviceCredentialsCallback(bool success
) {
495 // TODO(fgorski): This is one of the signals that store needs a rebuild.
499 void GCMClientImpl::UpdateRegistrationCallback(bool success
) {
500 // TODO(fgorski): This is one of the signals that store needs a rebuild.
504 void GCMClientImpl::Stop() {
505 device_checkin_info_
.Reset();
506 connection_factory_
.reset();
508 checkin_request_
.reset();
509 pending_registration_requests_
.clear();
510 state_
= INITIALIZED
;
514 void GCMClientImpl::CheckOut() {
516 gcm_store_
->Destroy(base::Bind(&GCMClientImpl::OnGCMStoreDestroyed
,
517 weak_ptr_factory_
.GetWeakPtr()));
520 void GCMClientImpl::Register(const std::string
& app_id
,
521 const std::vector
<std::string
>& sender_ids
) {
522 DCHECK_EQ(state_
, READY
);
524 // If the same sender ids is provided, return the cached registration ID
526 RegistrationInfoMap::const_iterator registrations_iter
=
527 registrations_
.find(app_id
);
528 if (registrations_iter
!= registrations_
.end() &&
529 registrations_iter
->second
->sender_ids
== sender_ids
) {
530 delegate_
->OnRegisterFinished(
531 app_id
, registrations_iter
->second
->registration_id
, SUCCESS
);
535 RegistrationRequest::RequestInfo
request_info(
536 device_checkin_info_
.android_id
,
537 device_checkin_info_
.secret
,
540 DCHECK_EQ(0u, pending_registration_requests_
.count(app_id
));
542 RegistrationRequest
* registration_request
=
543 new RegistrationRequest(gservices_settings_
.GetRegistrationURL(),
545 kDefaultBackoffPolicy
,
546 base::Bind(&GCMClientImpl::OnRegisterCompleted
,
547 weak_ptr_factory_
.GetWeakPtr(),
550 kMaxRegistrationRetries
,
551 url_request_context_getter_
,
553 pending_registration_requests_
[app_id
] = registration_request
;
554 registration_request
->Start();
557 void GCMClientImpl::OnRegisterCompleted(
558 const std::string
& app_id
,
559 const std::vector
<std::string
>& sender_ids
,
560 RegistrationRequest::Status status
,
561 const std::string
& registration_id
) {
565 PendingRegistrationRequests::iterator iter
=
566 pending_registration_requests_
.find(app_id
);
567 if (iter
== pending_registration_requests_
.end())
568 result
= UNKNOWN_ERROR
;
569 else if (status
== RegistrationRequest::INVALID_SENDER
)
570 result
= INVALID_PARAMETER
;
571 else if (registration_id
.empty())
572 result
= SERVER_ERROR
;
576 if (result
== SUCCESS
) {
578 linked_ptr
<RegistrationInfo
> registration(new RegistrationInfo
);
579 registration
->sender_ids
= sender_ids
;
580 registration
->registration_id
= registration_id
;
581 registrations_
[app_id
] = registration
;
583 // Save it in the persistent store.
584 gcm_store_
->AddRegistration(
587 base::Bind(&GCMClientImpl::UpdateRegistrationCallback
,
588 weak_ptr_factory_
.GetWeakPtr()));
591 delegate_
->OnRegisterFinished(
592 app_id
, result
== SUCCESS
? registration_id
: std::string(), result
);
594 if (iter
!= pending_registration_requests_
.end()) {
596 pending_registration_requests_
.erase(iter
);
600 void GCMClientImpl::Unregister(const std::string
& app_id
) {
601 DCHECK_EQ(state_
, READY
);
602 if (pending_unregistration_requests_
.count(app_id
) == 1)
605 // Remove from the cache and persistent store.
606 registrations_
.erase(app_id
);
607 gcm_store_
->RemoveRegistration(
609 base::Bind(&GCMClientImpl::UpdateRegistrationCallback
,
610 weak_ptr_factory_
.GetWeakPtr()));
612 UnregistrationRequest::RequestInfo
request_info(
613 device_checkin_info_
.android_id
,
614 device_checkin_info_
.secret
,
617 UnregistrationRequest
* unregistration_request
= new UnregistrationRequest(
618 gservices_settings_
.GetRegistrationURL(),
620 kDefaultBackoffPolicy
,
621 base::Bind(&GCMClientImpl::OnUnregisterCompleted
,
622 weak_ptr_factory_
.GetWeakPtr(),
624 url_request_context_getter_
,
626 pending_unregistration_requests_
[app_id
] = unregistration_request
;
627 unregistration_request
->Start();
630 void GCMClientImpl::OnUnregisterCompleted(
631 const std::string
& app_id
,
632 UnregistrationRequest::Status status
) {
633 DVLOG(1) << "Unregister completed for app: " << app_id
634 << " with " << (status
? "success." : "failure.");
635 delegate_
->OnUnregisterFinished(
637 status
== UnregistrationRequest::SUCCESS
? SUCCESS
: SERVER_ERROR
);
639 PendingUnregistrationRequests::iterator iter
=
640 pending_unregistration_requests_
.find(app_id
);
641 if (iter
== pending_unregistration_requests_
.end())
645 pending_unregistration_requests_
.erase(iter
);
648 void GCMClientImpl::OnGCMStoreDestroyed(bool success
) {
649 DLOG_IF(ERROR
, !success
) << "GCM store failed to be destroyed!";
650 UMA_HISTOGRAM_BOOLEAN("GCM.StoreDestroySucceeded", success
);
653 void GCMClientImpl::Send(const std::string
& app_id
,
654 const std::string
& receiver_id
,
655 const OutgoingMessage
& message
) {
656 DCHECK_EQ(state_
, READY
);
658 RecordOutgoingMessageToUMA(message
);
660 mcs_proto::DataMessageStanza stanza
;
661 stanza
.set_ttl(message
.time_to_live
);
662 stanza
.set_sent(clock_
->Now().ToInternalValue() /
663 base::Time::kMicrosecondsPerSecond
);
664 stanza
.set_id(message
.id
);
665 stanza
.set_from(kSendMessageFromValue
);
666 stanza
.set_to(receiver_id
);
667 stanza
.set_category(app_id
);
669 for (MessageData::const_iterator iter
= message
.data
.begin();
670 iter
!= message
.data
.end();
672 mcs_proto::AppData
* app_data
= stanza
.add_app_data();
673 app_data
->set_key(iter
->first
);
674 app_data
->set_value(iter
->second
);
677 MCSMessage
mcs_message(stanza
);
678 DVLOG(1) << "MCS message size: " << mcs_message
.size();
679 mcs_client_
->SendMessage(mcs_message
);
682 std::string
GCMClientImpl::GetStateString() const {
684 case GCMClientImpl::INITIALIZED
:
685 return "INITIALIZED";
686 case GCMClientImpl::UNINITIALIZED
:
687 return "UNINITIALIZED";
688 case GCMClientImpl::LOADING
:
690 case GCMClientImpl::INITIAL_DEVICE_CHECKIN
:
691 return "INITIAL_DEVICE_CHECKIN";
692 case GCMClientImpl::READY
:
696 return std::string();
700 void GCMClientImpl::SetRecording(bool recording
) {
701 recorder_
.SetRecording(recording
);
704 void GCMClientImpl::ClearActivityLogs() {
708 GCMClient::GCMStatistics
GCMClientImpl::GetStatistics() const {
709 GCMClient::GCMStatistics stats
;
710 stats
.gcm_client_created
= true;
711 stats
.is_recording
= recorder_
.is_recording();
712 stats
.gcm_client_state
= GetStateString();
713 stats
.connection_client_created
= mcs_client_
.get() != NULL
;
714 if (mcs_client_
.get()) {
715 stats
.connection_state
= mcs_client_
->GetStateString();
716 stats
.send_queue_size
= mcs_client_
->GetSendQueueSize();
717 stats
.resend_queue_size
= mcs_client_
->GetResendQueueSize();
719 if (device_checkin_info_
.android_id
> 0)
720 stats
.android_id
= device_checkin_info_
.android_id
;
721 recorder_
.CollectActivities(&stats
.recorded_activities
);
723 for (RegistrationInfoMap::const_iterator it
= registrations_
.begin();
724 it
!= registrations_
.end(); ++it
) {
725 stats
.registered_app_ids
.push_back(it
->first
);
730 void GCMClientImpl::OnActivityRecorded() {
731 delegate_
->OnActivityRecorded();
734 void GCMClientImpl::OnMessageReceivedFromMCS(const gcm::MCSMessage
& message
) {
735 switch (message
.tag()) {
736 case kLoginResponseTag
:
737 DVLOG(1) << "Login response received by GCM Client. Ignoring.";
739 case kDataMessageStanzaTag
:
740 DVLOG(1) << "A downstream message received. Processing...";
741 HandleIncomingMessage(message
);
744 NOTREACHED() << "Message with unexpected tag received by GCMClient";
749 void GCMClientImpl::OnMessageSentToMCS(int64 user_serial_number
,
750 const std::string
& app_id
,
751 const std::string
& message_id
,
752 MCSClient::MessageSendStatus status
) {
753 DCHECK_EQ(user_serial_number
, kDefaultUserSerialNumber
);
756 // TTL_EXCEEDED is singled out here, because it can happen long time after the
757 // message was sent. That is why it comes as |OnMessageSendError| event rather
758 // than |OnSendFinished|. SendErrorDetails.additional_data is left empty.
759 // All other errors will be raised immediately, through asynchronous callback.
760 // It is expected that TTL_EXCEEDED will be issued for a message that was
761 // previously issued |OnSendFinished| with status SUCCESS.
762 // For now, we do not report that the message has been sent and acked
764 // TODO(jianli): Consider adding UMA for this status.
765 if (status
== MCSClient::TTL_EXCEEDED
) {
766 SendErrorDetails send_error_details
;
767 send_error_details
.message_id
= message_id
;
768 send_error_details
.result
= GCMClient::TTL_EXCEEDED
;
769 delegate_
->OnMessageSendError(app_id
, send_error_details
);
770 } else if (status
!= MCSClient::SENT
) {
771 delegate_
->OnSendFinished(app_id
, message_id
, ToGCMClientResult(status
));
775 void GCMClientImpl::OnMCSError() {
776 // TODO(fgorski): For now it replaces the initialization method. Long term it
777 // should have an error or status passed in.
780 void GCMClientImpl::HandleIncomingMessage(const gcm::MCSMessage
& message
) {
783 const mcs_proto::DataMessageStanza
& data_message_stanza
=
784 reinterpret_cast<const mcs_proto::DataMessageStanza
&>(
785 message
.GetProtobuf());
786 DCHECK_EQ(data_message_stanza
.device_user_id(), kDefaultUserSerialNumber
);
788 // Copying all the data from the stanza to a MessageData object. When present,
789 // keys like kMessageTypeKey or kSendErrorMessageIdKey will be filtered out
791 MessageData message_data
;
792 for (int i
= 0; i
< data_message_stanza
.app_data_size(); ++i
) {
793 std::string key
= data_message_stanza
.app_data(i
).key();
794 message_data
[key
] = data_message_stanza
.app_data(i
).value();
797 MessageType message_type
= DATA_MESSAGE
;
798 MessageData::iterator iter
= message_data
.find(kMessageTypeKey
);
799 if (iter
!= message_data
.end()) {
800 message_type
= DecodeMessageType(iter
->second
);
801 message_data
.erase(iter
);
804 switch (message_type
) {
806 HandleIncomingDataMessage(data_message_stanza
, message_data
);
808 case DELETED_MESSAGES
:
809 recorder_
.RecordDataMessageReceived(data_message_stanza
.category(),
810 data_message_stanza
.from(),
811 data_message_stanza
.ByteSize(),
813 GCMStatsRecorder::DELETED_MESSAGES
);
814 delegate_
->OnMessagesDeleted(data_message_stanza
.category());
817 HandleIncomingSendError(data_message_stanza
, message_data
);
820 default: // Treat default the same as UNKNOWN.
821 DVLOG(1) << "Unknown message_type received. Message ignored. "
822 << "App ID: " << data_message_stanza
.category() << ".";
827 void GCMClientImpl::HandleIncomingDataMessage(
828 const mcs_proto::DataMessageStanza
& data_message_stanza
,
829 MessageData
& message_data
) {
830 std::string app_id
= data_message_stanza
.category();
832 // Drop the message when the app is not registered for the sender of the
834 RegistrationInfoMap::iterator iter
= registrations_
.find(app_id
);
835 bool not_registered
=
836 iter
== registrations_
.end() ||
837 std::find(iter
->second
->sender_ids
.begin(),
838 iter
->second
->sender_ids
.end(),
839 data_message_stanza
.from()) == iter
->second
->sender_ids
.end();
840 recorder_
.RecordDataMessageReceived(app_id
, data_message_stanza
.from(),
841 data_message_stanza
.ByteSize(), !not_registered
,
842 GCMStatsRecorder::DATA_MESSAGE
);
843 if (not_registered
) {
847 IncomingMessage incoming_message
;
848 incoming_message
.sender_id
= data_message_stanza
.from();
849 if (data_message_stanza
.has_token())
850 incoming_message
.collapse_key
= data_message_stanza
.token();
851 incoming_message
.data
= message_data
;
852 delegate_
->OnMessageReceived(app_id
, incoming_message
);
855 void GCMClientImpl::HandleIncomingSendError(
856 const mcs_proto::DataMessageStanza
& data_message_stanza
,
857 MessageData
& message_data
) {
858 SendErrorDetails send_error_details
;
859 send_error_details
.additional_data
= message_data
;
860 send_error_details
.result
= SERVER_ERROR
;
862 MessageData::iterator iter
=
863 send_error_details
.additional_data
.find(kSendErrorMessageIdKey
);
864 if (iter
!= send_error_details
.additional_data
.end()) {
865 send_error_details
.message_id
= iter
->second
;
866 send_error_details
.additional_data
.erase(iter
);
869 recorder_
.RecordIncomingSendError(
870 data_message_stanza
.category(),
871 data_message_stanza
.to(),
872 data_message_stanza
.id());
873 delegate_
->OnMessageSendError(data_message_stanza
.category(),