1 // Copyright (c) 2015 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 "chrome/browser/chromeos/policy/heartbeat_scheduler.h"
10 #include "base/bind_helpers.h"
11 #include "base/command_line.h"
12 #include "base/location.h"
13 #include "base/sequenced_task_runner.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/time/time.h"
16 #include "chrome/common/chrome_switches.h"
17 #include "components/gcm_driver/gcm_driver.h"
20 const int kMinHeartbeatIntervalMs
= 30 * 1000; // 30 seconds
21 const int kMaxHeartbeatIntervalMs
= 24 * 60 * 60 * 1000; // 24 hours
23 // Our sender ID we send up with all of our GCM messages.
24 const char* kHeartbeatGCMAppID
= "com.google.chromeos.monitoring";
26 // The default destination we send our GCM messages to.
27 const char* kHeartbeatGCMDestinationID
= "1013309121859";
28 const char* kHeartbeatGCMSenderSuffix
= "@google.com";
30 const char* kMonitoringMessageTypeKey
= "type";
31 const char* kHeartbeatTimestampKey
= "timestamp";
32 const char* kHeartbeatDomainNameKey
= "domain_name";
33 const char* kHeartbeatDeviceIDKey
= "device_id";
34 const char* kHeartbeatTypeValue
= "hb";
36 // If we get an error registering with GCM, try again in two minutes.
37 const int64 kRegistrationRetryDelayMs
= 2 * 60 * 1000;
39 // Returns the destination ID for GCM heartbeats.
40 std::string
GetDestinationID() {
41 std::string receiver_id
= kHeartbeatGCMDestinationID
;
42 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
43 switches::kMonitoringDestinationID
)) {
44 receiver_id
= base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
45 switches::kMonitoringDestinationID
);
54 const int64
HeartbeatScheduler::kDefaultHeartbeatIntervalMs
=
55 2 * 60 * 1000; // 2 minutes
57 // Helper class used to manage GCM registration (handles retrying after
59 class HeartbeatRegistrationHelper
{
61 typedef base::Callback
<void(const std::string
& registration_id
)>
62 RegistrationHelperCallback
;
64 HeartbeatRegistrationHelper(
65 gcm::GCMDriver
* gcm_driver
,
66 const scoped_refptr
<base::SequencedTaskRunner
>& task_runner
);
68 void Register(const RegistrationHelperCallback
& callback
);
71 void AttemptRegistration();
73 // Callback invoked once a registration attempt has finished.
74 void OnRegisterAttemptComplete(const std::string
& registration_id
,
75 gcm::GCMClient::Result result
);
77 // GCMDriver to use to register.
78 gcm::GCMDriver
* const gcm_driver_
;
80 // Callback to invoke when we have completed GCM registration.
81 RegistrationHelperCallback callback_
;
83 // TaskRunner used for scheduling retry attempts.
84 const scoped_refptr
<base::SequencedTaskRunner
> task_runner_
;
86 // Should remain the last member so it will be destroyed first and
87 // invalidate all weak pointers.
88 base::WeakPtrFactory
<HeartbeatRegistrationHelper
> weak_factory_
;
90 DISALLOW_COPY_AND_ASSIGN(HeartbeatRegistrationHelper
);
93 HeartbeatRegistrationHelper::HeartbeatRegistrationHelper(
94 gcm::GCMDriver
* gcm_driver
,
95 const scoped_refptr
<base::SequencedTaskRunner
>& task_runner
)
96 : gcm_driver_(gcm_driver
),
97 task_runner_(task_runner
),
101 void HeartbeatRegistrationHelper::Register(
102 const RegistrationHelperCallback
& callback
) {
103 // Should only call Register() once.
104 DCHECK(callback_
.is_null());
105 callback_
= callback
;
106 AttemptRegistration();
109 void HeartbeatRegistrationHelper::AttemptRegistration() {
110 std::vector
<std::string
> destinations
;
111 destinations
.push_back(GetDestinationID());
112 gcm_driver_
->Register(
115 base::Bind(&HeartbeatRegistrationHelper::OnRegisterAttemptComplete
,
116 weak_factory_
.GetWeakPtr()));
119 void HeartbeatRegistrationHelper::OnRegisterAttemptComplete(
120 const std::string
& registration_id
, gcm::GCMClient::Result result
) {
121 DVLOG(1) << "Received Register() response: " << result
;
122 // TODO(atwilson): Track GCM errors via UMA (http://crbug.com/459238).
124 case gcm::GCMClient::SUCCESS
:
126 // Copy the callback, because the callback may free this object and
127 // we don't want to free the callback object and any bound variables
128 // until the callback exits.
129 RegistrationHelperCallback callback
= callback_
;
130 callback
.Run(registration_id
);
131 // This helper may be freed now, so do not access any member variables
136 case gcm::GCMClient::NETWORK_ERROR
:
137 case gcm::GCMClient::SERVER_ERROR
:
138 // Transient error - try again after a delay.
139 task_runner_
->PostDelayedTask(
141 base::Bind(&HeartbeatRegistrationHelper::AttemptRegistration
,
142 weak_factory_
.GetWeakPtr()),
143 base::TimeDelta::FromMilliseconds(kRegistrationRetryDelayMs
));
146 case gcm::GCMClient::INVALID_PARAMETER
:
147 case gcm::GCMClient::UNKNOWN_ERROR
:
148 case gcm::GCMClient::GCM_DISABLED
:
149 // No need to bother retrying in the case of one of these fatal errors.
150 // This means that heartbeats will remain disabled until the next
152 DLOG(ERROR
) << "Fatal GCM Registration error: " << result
;
155 case gcm::GCMClient::ASYNC_OPERATION_PENDING
:
156 case gcm::GCMClient::TTL_EXCEEDED
:
158 NOTREACHED() << "Unexpected GCMDriver::Register() result: " << result
;
163 HeartbeatScheduler::HeartbeatScheduler(
164 gcm::GCMDriver
* driver
,
165 policy::CloudPolicyClient
* cloud_policy_client
,
166 const std::string
& enrollment_domain
,
167 const std::string
& device_id
,
168 const scoped_refptr
<base::SequencedTaskRunner
>& task_runner
)
169 : task_runner_(task_runner
),
170 enrollment_domain_(enrollment_domain
),
171 device_id_(device_id
),
172 heartbeat_enabled_(false),
174 base::TimeDelta::FromMilliseconds(kDefaultHeartbeatIntervalMs
)),
175 cloud_policy_client_(cloud_policy_client
),
177 weak_factory_(this) {
178 // If no GCMDriver (e.g. this is loaded as part of an unrelated unit test)
179 // do nothing as no heartbeats can be sent.
183 heartbeat_frequency_observer_
=
184 chromeos::CrosSettings::Get()->AddSettingsObserver(
185 chromeos::kHeartbeatFrequency
,
186 base::Bind(&HeartbeatScheduler::RefreshHeartbeatSettings
,
187 base::Unretained(this)));
189 heartbeat_enabled_observer_
=
190 chromeos::CrosSettings::Get()->AddSettingsObserver(
191 chromeos::kHeartbeatEnabled
,
192 base::Bind(&HeartbeatScheduler::RefreshHeartbeatSettings
,
193 base::Unretained(this)));
195 // Update the heartbeat frequency from settings. This will trigger a
196 // heartbeat as appropriate once the settings have been refreshed.
197 RefreshHeartbeatSettings();
200 void HeartbeatScheduler::RefreshHeartbeatSettings() {
201 // Attempt to fetch the current value of the reporting settings.
202 // If trusted values are not available, register this function to be called
203 // back when they are available.
204 chromeos::CrosSettings
* settings
= chromeos::CrosSettings::Get();
205 if (chromeos::CrosSettingsProvider::TRUSTED
!= settings
->PrepareTrustedValues(
206 base::Bind(&HeartbeatScheduler::RefreshHeartbeatSettings
,
207 weak_factory_
.GetWeakPtr()))) {
211 // CrosSettings are trusted - update our cached settings (we cache the
212 // value because CrosSettings can become untrusted at arbitrary times and we
213 // want to use the last trusted value).
215 if (settings
->GetInteger(chromeos::kHeartbeatFrequency
, &frequency
))
216 heartbeat_interval_
= EnsureValidHeartbeatInterval(
217 base::TimeDelta::FromMilliseconds(frequency
));
220 if (settings
->GetBoolean(chromeos::kHeartbeatEnabled
, &enabled
))
221 heartbeat_enabled_
= enabled
;
223 if (!heartbeat_enabled_
) {
224 // Heartbeats are no longer enabled - cancel our callback and any
225 // outstanding registration attempts and disconnect from GCM so the
226 // connection can be shut down. If heartbeats are re-enabled later, we
227 // will re-register with GCM.
228 heartbeat_callback_
.Cancel();
231 // Schedule a new upload with the new frequency.
232 ScheduleNextHeartbeat();
235 DVLOG(1) << "heartbeat enabled: " << heartbeat_enabled_
;
236 DVLOG(1) << "heartbeat frequency: " << heartbeat_interval_
;
239 void HeartbeatScheduler::ShutdownGCM() {
240 registration_helper_
.reset();
241 registration_id_
.clear();
242 if (registered_app_handler_
) {
243 registered_app_handler_
= false;
244 gcm_driver_
->RemoveAppHandler(kHeartbeatGCMAppID
);
248 base::TimeDelta
HeartbeatScheduler::EnsureValidHeartbeatInterval(
249 const base::TimeDelta
& interval
) {
250 const base::TimeDelta min
= base::TimeDelta::FromMilliseconds(
251 kMinHeartbeatIntervalMs
);
252 const base::TimeDelta max
= base::TimeDelta::FromMilliseconds(
253 kMaxHeartbeatIntervalMs
);
254 if (interval
< min
) {
255 DLOG(WARNING
) << "Invalid heartbeat interval: " << interval
;
258 if (interval
> max
) {
259 DLOG(WARNING
) << "Invalid heartbeat interval: " << interval
;
265 void HeartbeatScheduler::ScheduleNextHeartbeat() {
266 // Do nothing if heartbeats are disabled.
267 if (!heartbeat_enabled_
)
270 if (registration_id_
.empty()) {
271 // We are not registered with the GCM service yet, so kick off registration.
272 if (!registration_helper_
) {
273 // Add ourselves as an AppHandler - this is required in order to setup
275 registered_app_handler_
= true;
276 gcm_driver_
->AddAppHandler(kHeartbeatGCMAppID
, this);
277 registration_helper_
.reset(new HeartbeatRegistrationHelper(
278 gcm_driver_
, task_runner_
));
279 registration_helper_
->Register(
280 base::Bind(&HeartbeatScheduler::OnRegistrationComplete
,
281 weak_factory_
.GetWeakPtr()));
286 // Calculate when to fire off the next update (if it should have already
287 // happened, this yields a TimeDelta of 0).
288 base::TimeDelta delay
= std::max(
289 last_heartbeat_
+ heartbeat_interval_
- base::Time::NowFromSystemTime(),
292 heartbeat_callback_
.Reset(base::Bind(&HeartbeatScheduler::SendHeartbeat
,
293 base::Unretained(this)));
294 task_runner_
->PostDelayedTask(
295 FROM_HERE
, heartbeat_callback_
.callback(), delay
);
298 void HeartbeatScheduler::OnRegistrationComplete(
299 const std::string
& registration_id
) {
300 DCHECK(!registration_id
.empty());
301 registration_helper_
.reset();
302 registration_id_
= registration_id
;
304 if (cloud_policy_client_
) {
305 // TODO(binjin): Avoid sending the same GCM id to the server.
306 // See http://crbug.com/516375
307 cloud_policy_client_
->UpdateGcmId(
309 base::Bind(&HeartbeatScheduler::OnGcmIdUpdateRequestSent
,
310 weak_factory_
.GetWeakPtr()));
313 // Now that GCM registration is complete, start sending heartbeats.
314 ScheduleNextHeartbeat();
317 void HeartbeatScheduler::SendHeartbeat() {
318 DCHECK(!registration_id_
.empty());
319 if (!gcm_driver_
|| !heartbeat_enabled_
)
322 gcm::OutgoingMessage message
;
323 message
.time_to_live
= heartbeat_interval_
.InSeconds();
324 // Just use the current timestamp as the message ID - if the user changes the
325 // time and we send a message with the same ID that we previously used, no
326 // big deal (the new message will replace the old, which is the behavior we
328 // https://developer.chrome.com/apps/cloudMessaging#send_messages
329 message
.id
= base::Int64ToString(
330 base::Time::NowFromSystemTime().ToInternalValue());
331 message
.data
[kMonitoringMessageTypeKey
] = kHeartbeatTypeValue
;
332 message
.data
[kHeartbeatTimestampKey
] = base::Int64ToString(
333 base::Time::NowFromSystemTime().ToJavaTime());
334 message
.data
[kHeartbeatDomainNameKey
] = enrollment_domain_
;
335 message
.data
[kHeartbeatDeviceIDKey
] = device_id_
;
336 gcm_driver_
->Send(kHeartbeatGCMAppID
,
337 GetDestinationID() + kHeartbeatGCMSenderSuffix
,
339 base::Bind(&HeartbeatScheduler::OnHeartbeatSent
,
340 weak_factory_
.GetWeakPtr()));
343 void HeartbeatScheduler::OnHeartbeatSent(const std::string
& message_id
,
344 gcm::GCMClient::Result result
) {
345 DVLOG(1) << "Monitoring heartbeat sent - result = " << result
;
346 // Don't care if the result was successful or not - just schedule the next
348 DLOG_IF(ERROR
, result
!= gcm::GCMClient::SUCCESS
) <<
349 "Error sending monitoring heartbeat: " << result
;
350 last_heartbeat_
= base::Time::NowFromSystemTime();
351 ScheduleNextHeartbeat();
354 HeartbeatScheduler::~HeartbeatScheduler() {
358 void HeartbeatScheduler::ShutdownHandler() {
359 // This should never be called, because BrowserProcessImpl::StartTearDown()
360 // should shutdown the BrowserPolicyConnector (which destroys this object)
361 // before the GCMDriver. Our goal is to make sure that this object is always
362 // shutdown before GCMDriver is shut down, rather than trying to handle the
363 // case when GCMDriver goes away.
364 NOTREACHED() << "HeartbeatScheduler should be destroyed before GCMDriver";
367 void HeartbeatScheduler::OnMessage(const std::string
& app_id
,
368 const gcm::IncomingMessage
& message
) {
369 // Should never be called because we don't get any incoming messages
371 NOTREACHED() << "Received incoming message for " << app_id
;
374 void HeartbeatScheduler::OnMessagesDeleted(const std::string
& app_id
) {
377 void HeartbeatScheduler::OnSendError(
378 const std::string
& app_id
,
379 const gcm::GCMClient::SendErrorDetails
& details
) {
380 // Ignore send errors - we already are notified above in OnHeartbeatSent().
383 void HeartbeatScheduler::OnSendAcknowledged(const std::string
& app_id
,
384 const std::string
& message_id
) {
385 DVLOG(1) << "Heartbeat sent with message_id: " << message_id
;
388 void HeartbeatScheduler::OnGcmIdUpdateRequestSent(bool success
) {
389 // TODO(binjin): Handle the failure, probably by exponential backoff.
390 LOG_IF(WARNING
, !success
) << "Failed to send GCM id to DM server";
393 } // namespace policy