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"
9 #include "base/command_line.h"
10 #include "base/location.h"
11 #include "base/sequenced_task_runner.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/time/time.h"
14 #include "chrome/common/chrome_switches.h"
15 #include "components/gcm_driver/gcm_driver.h"
18 const int kMinHeartbeatIntervalMs
= 30 * 1000; // 30 seconds
19 const int kMaxHeartbeatIntervalMs
= 24 * 60 * 60 * 1000; // 24 hours
21 // Our sender ID we send up with all of our GCM messages.
22 const char* kHeartbeatGCMAppID
= "com.google.chromeos.monitoring";
24 // The default destination we send our GCM messages to.
25 const char* kHeartbeatGCMDestinationID
= "1013309121859";
26 const char* kHeartbeatGCMSenderSuffix
= "@google.com";
28 const char* kMonitoringMessageTypeKey
= "type";
29 const char* kHeartbeatTimestampKey
= "timestamp";
30 const char* kHeartbeatDomainNameKey
= "domain_name";
31 const char* kHeartbeatDeviceIDKey
= "device_id";
32 const char* kHeartbeatTypeValue
= "hb";
34 // If we get an error registering with GCM, try again in two minutes.
35 const int64 kRegistrationRetryDelayMs
= 2 * 60 * 1000;
37 // Returns the destination ID for GCM heartbeats.
38 std::string
GetDestinationID() {
39 std::string receiver_id
= kHeartbeatGCMDestinationID
;
40 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
41 switches::kMonitoringDestinationID
)) {
42 receiver_id
= base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
43 switches::kMonitoringDestinationID
);
52 const int64
HeartbeatScheduler::kDefaultHeartbeatIntervalMs
=
53 2 * 60 * 1000; // 2 minutes
55 // Helper class used to manage GCM registration (handles retrying after
57 class HeartbeatRegistrationHelper
{
59 typedef base::Callback
<void(const std::string
& registration_id
)>
60 RegistrationHelperCallback
;
62 HeartbeatRegistrationHelper(
63 gcm::GCMDriver
* gcm_driver
,
64 const scoped_refptr
<base::SequencedTaskRunner
>& task_runner
);
66 void Register(const RegistrationHelperCallback
& callback
);
69 void AttemptRegistration();
71 // Callback invoked once a registration attempt has finished.
72 void OnRegisterAttemptComplete(const std::string
& registration_id
,
73 gcm::GCMClient::Result result
);
75 // GCMDriver to use to register.
76 gcm::GCMDriver
* const gcm_driver_
;
78 // Callback to invoke when we have completed GCM registration.
79 RegistrationHelperCallback callback_
;
81 // TaskRunner used for scheduling retry attempts.
82 const scoped_refptr
<base::SequencedTaskRunner
> task_runner_
;
84 // Should remain the last member so it will be destroyed first and
85 // invalidate all weak pointers.
86 base::WeakPtrFactory
<HeartbeatRegistrationHelper
> weak_factory_
;
88 DISALLOW_COPY_AND_ASSIGN(HeartbeatRegistrationHelper
);
91 HeartbeatRegistrationHelper::HeartbeatRegistrationHelper(
92 gcm::GCMDriver
* gcm_driver
,
93 const scoped_refptr
<base::SequencedTaskRunner
>& task_runner
)
94 : gcm_driver_(gcm_driver
),
95 task_runner_(task_runner
),
99 void HeartbeatRegistrationHelper::Register(
100 const RegistrationHelperCallback
& callback
) {
101 // Should only call Register() once.
102 DCHECK(callback_
.is_null());
103 callback_
= callback
;
104 AttemptRegistration();
107 void HeartbeatRegistrationHelper::AttemptRegistration() {
108 std::vector
<std::string
> destinations
;
109 destinations
.push_back(GetDestinationID());
110 gcm_driver_
->Register(
113 base::Bind(&HeartbeatRegistrationHelper::OnRegisterAttemptComplete
,
114 weak_factory_
.GetWeakPtr()));
117 void HeartbeatRegistrationHelper::OnRegisterAttemptComplete(
118 const std::string
& registration_id
, gcm::GCMClient::Result result
) {
119 DVLOG(1) << "Received Register() response: " << result
;
120 // TODO(atwilson): Track GCM errors via UMA (http://crbug.com/459238).
122 case gcm::GCMClient::SUCCESS
:
124 // Copy the callback, because the callback may free this object and
125 // we don't want to free the callback object and any bound variables
126 // until the callback exits.
127 RegistrationHelperCallback callback
= callback_
;
128 callback
.Run(registration_id
);
129 // This helper may be freed now, so do not access any member variables
134 case gcm::GCMClient::NETWORK_ERROR
:
135 case gcm::GCMClient::SERVER_ERROR
:
136 // Transient error - try again after a delay.
137 task_runner_
->PostDelayedTask(
139 base::Bind(&HeartbeatRegistrationHelper::AttemptRegistration
,
140 weak_factory_
.GetWeakPtr()),
141 base::TimeDelta::FromMilliseconds(kRegistrationRetryDelayMs
));
144 case gcm::GCMClient::INVALID_PARAMETER
:
145 case gcm::GCMClient::UNKNOWN_ERROR
:
146 case gcm::GCMClient::GCM_DISABLED
:
147 // No need to bother retrying in the case of one of these fatal errors.
148 // This means that heartbeats will remain disabled until the next
150 DLOG(ERROR
) << "Fatal GCM Registration error: " << result
;
153 case gcm::GCMClient::ASYNC_OPERATION_PENDING
:
154 case gcm::GCMClient::TTL_EXCEEDED
:
156 NOTREACHED() << "Unexpected GCMDriver::Register() result: " << result
;
161 HeartbeatScheduler::HeartbeatScheduler(
162 gcm::GCMDriver
* driver
,
163 const std::string
& enrollment_domain
,
164 const std::string
& device_id
,
165 const scoped_refptr
<base::SequencedTaskRunner
>& task_runner
)
166 : task_runner_(task_runner
),
167 enrollment_domain_(enrollment_domain
),
168 device_id_(device_id
),
169 heartbeat_enabled_(false),
170 heartbeat_interval_(base::TimeDelta::FromMilliseconds(
171 kDefaultHeartbeatIntervalMs
)),
173 weak_factory_(this) {
174 // If no GCMDriver (e.g. this is loaded as part of an unrelated unit test)
175 // do nothing as no heartbeats can be sent.
179 heartbeat_frequency_observer_
=
180 chromeos::CrosSettings::Get()->AddSettingsObserver(
181 chromeos::kHeartbeatFrequency
,
182 base::Bind(&HeartbeatScheduler::RefreshHeartbeatSettings
,
183 base::Unretained(this)));
185 heartbeat_enabled_observer_
=
186 chromeos::CrosSettings::Get()->AddSettingsObserver(
187 chromeos::kHeartbeatEnabled
,
188 base::Bind(&HeartbeatScheduler::RefreshHeartbeatSettings
,
189 base::Unretained(this)));
191 // Update the heartbeat frequency from settings. This will trigger a
192 // heartbeat as appropriate once the settings have been refreshed.
193 RefreshHeartbeatSettings();
196 void HeartbeatScheduler::RefreshHeartbeatSettings() {
197 // Attempt to fetch the current value of the reporting settings.
198 // If trusted values are not available, register this function to be called
199 // back when they are available.
200 chromeos::CrosSettings
* settings
= chromeos::CrosSettings::Get();
201 if (chromeos::CrosSettingsProvider::TRUSTED
!= settings
->PrepareTrustedValues(
202 base::Bind(&HeartbeatScheduler::RefreshHeartbeatSettings
,
203 weak_factory_
.GetWeakPtr()))) {
207 // CrosSettings are trusted - update our cached settings (we cache the
208 // value because CrosSettings can become untrusted at arbitrary times and we
209 // want to use the last trusted value).
211 if (settings
->GetInteger(chromeos::kHeartbeatFrequency
, &frequency
))
212 heartbeat_interval_
= EnsureValidHeartbeatInterval(
213 base::TimeDelta::FromMilliseconds(frequency
));
216 if (settings
->GetBoolean(chromeos::kHeartbeatEnabled
, &enabled
))
217 heartbeat_enabled_
= enabled
;
219 if (!heartbeat_enabled_
) {
220 // Heartbeats are no longer enabled - cancel our callback and any
221 // outstanding registration attempts and disconnect from GCM so the
222 // connection can be shut down. If heartbeats are re-enabled later, we
223 // will re-register with GCM.
224 heartbeat_callback_
.Cancel();
227 // Schedule a new upload with the new frequency.
228 ScheduleNextHeartbeat();
231 DVLOG(1) << "heartbeat enabled: " << heartbeat_enabled_
;
232 DVLOG(1) << "heartbeat frequency: " << heartbeat_interval_
;
235 void HeartbeatScheduler::ShutdownGCM() {
236 registration_helper_
.reset();
237 registration_id_
.clear();
238 if (registered_app_handler_
) {
239 registered_app_handler_
= false;
240 gcm_driver_
->RemoveAppHandler(kHeartbeatGCMAppID
);
244 base::TimeDelta
HeartbeatScheduler::EnsureValidHeartbeatInterval(
245 const base::TimeDelta
& interval
) {
246 const base::TimeDelta min
= base::TimeDelta::FromMilliseconds(
247 kMinHeartbeatIntervalMs
);
248 const base::TimeDelta max
= base::TimeDelta::FromMilliseconds(
249 kMaxHeartbeatIntervalMs
);
250 if (interval
< min
) {
251 DLOG(WARNING
) << "Invalid heartbeat interval: " << interval
;
254 if (interval
> max
) {
255 DLOG(WARNING
) << "Invalid heartbeat interval: " << interval
;
261 void HeartbeatScheduler::ScheduleNextHeartbeat() {
262 // Do nothing if heartbeats are disabled.
263 if (!heartbeat_enabled_
)
266 if (registration_id_
.empty()) {
267 // We are not registered with the GCM service yet, so kick off registration.
268 if (!registration_helper_
) {
269 // Add ourselves as an AppHandler - this is required in order to setup
271 registered_app_handler_
= true;
272 gcm_driver_
->AddAppHandler(kHeartbeatGCMAppID
, this);
273 registration_helper_
.reset(new HeartbeatRegistrationHelper(
274 gcm_driver_
, task_runner_
));
275 registration_helper_
->Register(
276 base::Bind(&HeartbeatScheduler::OnRegistrationComplete
,
277 weak_factory_
.GetWeakPtr()));
282 // Calculate when to fire off the next update (if it should have already
283 // happened, this yields a TimeDelta of 0).
284 base::TimeDelta delay
= std::max(
285 last_heartbeat_
+ heartbeat_interval_
- base::Time::NowFromSystemTime(),
288 heartbeat_callback_
.Reset(base::Bind(&HeartbeatScheduler::SendHeartbeat
,
289 base::Unretained(this)));
290 task_runner_
->PostDelayedTask(
291 FROM_HERE
, heartbeat_callback_
.callback(), delay
);
294 void HeartbeatScheduler::OnRegistrationComplete(
295 const std::string
& registration_id
) {
296 DCHECK(!registration_id
.empty());
297 registration_helper_
.reset();
298 registration_id_
= registration_id
;
300 // Now that GCM registration is complete, start sending heartbeats.
301 ScheduleNextHeartbeat();
304 void HeartbeatScheduler::SendHeartbeat() {
305 DCHECK(!registration_id_
.empty());
306 if (!gcm_driver_
|| !heartbeat_enabled_
)
309 gcm::GCMClient::OutgoingMessage message
;
310 message
.time_to_live
= heartbeat_interval_
.InSeconds();
311 // Just use the current timestamp as the message ID - if the user changes the
312 // time and we send a message with the same ID that we previously used, no
313 // big deal (the new message will replace the old, which is the behavior we
315 // https://developer.chrome.com/apps/cloudMessaging#send_messages
316 message
.id
= base::Int64ToString(
317 base::Time::NowFromSystemTime().ToInternalValue());
318 message
.data
[kMonitoringMessageTypeKey
] = kHeartbeatTypeValue
;
319 message
.data
[kHeartbeatTimestampKey
] = base::Int64ToString(
320 base::Time::NowFromSystemTime().ToJavaTime());
321 message
.data
[kHeartbeatDomainNameKey
] = enrollment_domain_
;
322 message
.data
[kHeartbeatDeviceIDKey
] = device_id_
;
323 gcm_driver_
->Send(kHeartbeatGCMAppID
,
324 GetDestinationID() + kHeartbeatGCMSenderSuffix
,
326 base::Bind(&HeartbeatScheduler::OnHeartbeatSent
,
327 weak_factory_
.GetWeakPtr()));
330 void HeartbeatScheduler::OnHeartbeatSent(const std::string
& message_id
,
331 gcm::GCMClient::Result result
) {
332 DVLOG(1) << "Monitoring heartbeat sent - result = " << result
;
333 // Don't care if the result was successful or not - just schedule the next
335 DLOG_IF(ERROR
, result
!= gcm::GCMClient::SUCCESS
) <<
336 "Error sending monitoring heartbeat: " << result
;
337 last_heartbeat_
= base::Time::NowFromSystemTime();
338 ScheduleNextHeartbeat();
341 HeartbeatScheduler::~HeartbeatScheduler() {
345 void HeartbeatScheduler::ShutdownHandler() {
346 // This should never be called, because BrowserProcessImpl::StartTearDown()
347 // should shutdown the BrowserPolicyConnector (which destroys this object)
348 // before the GCMDriver. Our goal is to make sure that this object is always
349 // shutdown before GCMDriver is shut down, rather than trying to handle the
350 // case when GCMDriver goes away.
351 NOTREACHED() << "HeartbeatScheduler should be destroyed before GCMDriver";
354 void HeartbeatScheduler::OnMessage(
355 const std::string
& app_id
,
356 const gcm::GCMClient::IncomingMessage
& message
) {
357 // Should never be called because we don't get any incoming messages
359 NOTREACHED() << "Received incoming message for " << app_id
;
362 void HeartbeatScheduler::OnMessagesDeleted(const std::string
& app_id
) {
365 void HeartbeatScheduler::OnSendError(
366 const std::string
& app_id
,
367 const gcm::GCMClient::SendErrorDetails
& details
) {
368 // Ignore send errors - we already are notified above in OnHeartbeatSent().
371 void HeartbeatScheduler::OnSendAcknowledged(const std::string
& app_id
,
372 const std::string
& message_id
) {
373 DVLOG(1) << "Heartbeat sent with message_id: " << message_id
;
376 } // namespace policy