#include "chrome/browser/ash/policy/uploading/heartbeat_scheduler.h"
#include <memory>
#include <vector>
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "chrome/common/chrome_switches.h"
#include "components/gcm_driver/gcm_driver.h"
namespace policy {
namespace {
constexpr base::TimeDelta kMinHeartbeatInterval = base::Seconds(30);
constexpr base::TimeDelta kMaxHeartbeatInterval = base::Days(1);
const char kHeartbeatGCMAppID[] = "com.google.chromeos.monitoring";
const char kHeartbeatGCMDestinationID[] = "1013309121859";
const char kHeartbeatGCMSenderSuffix[] = "@google.com";
const char kUpstreamNotificationSignUpDestinationID[] =
"https://gcm.googleapis.com/gcm/gcm.event_tracker";
const char kUpstreamNotificationSignUpListeningEvents[] =
"7";
const char kGcmMessageTypeKey[] = "type";
const char kHeartbeatTimestampKey[] = "timestamp";
const char kHeartbeatCustomerIdKey[] = "customer_id";
const char kHeartbeatDeviceIDKey[] = "device_id";
const char kHeartbeatTypeValue[] = "hb";
const char kUpstreamNotificationNotifyKey[] = "notify";
const char kUpstreamNotificationRegIdKey[] = "registration_id";
constexpr base::TimeDelta kRegistrationRetryDelay = base::Minutes(2);
const char kHeartbeatSchedulerScope[] =
"policy.heartbeat_scheduler.upstream_notification";
std::string GetDestinationID() {
std::string receiver_id = kHeartbeatGCMDestinationID;
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kMonitoringDestinationID)) {
receiver_id = base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kMonitoringDestinationID);
}
return receiver_id;
}
}
const base::TimeDelta HeartbeatScheduler::kDefaultHeartbeatInterval =
base::Minutes(2);
class HeartbeatRegistrationHelper {
public:
using RegistrationHelperCallback =
base::OnceCallback<void(const std::string& registration_id)>;
HeartbeatRegistrationHelper(
gcm::GCMDriver* gcm_driver,
const scoped_refptr<base::SequencedTaskRunner>& task_runner);
HeartbeatRegistrationHelper(const HeartbeatRegistrationHelper&) = delete;
HeartbeatRegistrationHelper& operator=(const HeartbeatRegistrationHelper&) =
delete;
void Register(RegistrationHelperCallback callback);
private:
void AttemptRegistration();
void OnRegisterAttemptComplete(const std::string& registration_id,
gcm::GCMClient::Result result);
const raw_ptr<gcm::GCMDriver> gcm_driver_;
RegistrationHelperCallback callback_;
const scoped_refptr<base::SequencedTaskRunner> task_runner_;
base::WeakPtrFactory<HeartbeatRegistrationHelper> weak_factory_{this};
};
HeartbeatRegistrationHelper::HeartbeatRegistrationHelper(
gcm::GCMDriver* gcm_driver,
const scoped_refptr<base::SequencedTaskRunner>& task_runner)
: gcm_driver_(gcm_driver), task_runner_(task_runner) {}
void HeartbeatRegistrationHelper::Register(
RegistrationHelperCallback callback) {
DCHECK(callback_.is_null());
callback_ = std::move(callback);
AttemptRegistration();
}
void HeartbeatRegistrationHelper::AttemptRegistration() {
std::vector<std::string> destinations;
destinations.push_back(GetDestinationID());
gcm_driver_->Register(
kHeartbeatGCMAppID, destinations,
base::BindOnce(&HeartbeatRegistrationHelper::OnRegisterAttemptComplete,
weak_factory_.GetWeakPtr()));
}
void HeartbeatRegistrationHelper::OnRegisterAttemptComplete(
const std::string& registration_id,
gcm::GCMClient::Result result) {
DVLOG(1) << "Received Register() response: " << result;
switch (result) {
case gcm::GCMClient::SUCCESS:
std::move(callback_).Run(registration_id);
return;
case gcm::GCMClient::NETWORK_ERROR:
case gcm::GCMClient::SERVER_ERROR:
task_runner_->PostDelayedTask(
FROM_HERE,
base::BindOnce(&HeartbeatRegistrationHelper::AttemptRegistration,
weak_factory_.GetWeakPtr()),
kRegistrationRetryDelay);
break;
case gcm::GCMClient::INVALID_PARAMETER:
case gcm::GCMClient::UNKNOWN_ERROR:
case gcm::GCMClient::GCM_DISABLED:
DLOG(ERROR) << "Fatal GCM Registration error: " << result;
break;
case gcm::GCMClient::ASYNC_OPERATION_PENDING:
case gcm::GCMClient::TTL_EXCEEDED:
default:
NOTREACHED() << "Unexpected GCMDriver::Register() result: " << result;
}
}
HeartbeatScheduler::HeartbeatScheduler(
gcm::GCMDriver* driver,
CloudPolicyClient* cloud_policy_client,
CloudPolicyStore* cloud_policy_store,
const std::string& device_id,
const scoped_refptr<base::SequencedTaskRunner>& task_runner)
: task_runner_(task_runner),
device_id_(device_id),
heartbeat_enabled_(false),
heartbeat_interval_(kDefaultHeartbeatInterval),
cloud_policy_client_(cloud_policy_client),
cloud_policy_store_(cloud_policy_store),
gcm_driver_(driver) {
if (!gcm_driver_)
return;
heartbeat_frequency_subscription_ =
ash::CrosSettings::Get()->AddSettingsObserver(
ash::kHeartbeatFrequency,
base::BindRepeating(&HeartbeatScheduler::RefreshHeartbeatSettings,
base::Unretained(this)));
heartbeat_enabled_subscription_ =
ash::CrosSettings::Get()->AddSettingsObserver(
ash::kHeartbeatEnabled,
base::BindRepeating(&HeartbeatScheduler::RefreshHeartbeatSettings,
base::Unretained(this)));
RefreshHeartbeatSettings();
gcm_driver_->AddHeartbeatInterval(kHeartbeatSchedulerScope,
heartbeat_interval_.InMilliseconds());
}
void HeartbeatScheduler::RefreshHeartbeatSettings() {
ash::CrosSettings* settings = ash::CrosSettings::Get();
if (ash::CrosSettingsProvider::TRUSTED !=
settings->PrepareTrustedValues(
base::BindOnce(&HeartbeatScheduler::RefreshHeartbeatSettings,
weak_factory_.GetWeakPtr()))) {
return;
}
int frequency;
if (settings->GetInteger(ash::kHeartbeatFrequency, &frequency)) {
heartbeat_interval_ =
EnsureValidHeartbeatInterval(base::Milliseconds(frequency));
}
gcm_driver_->AddHeartbeatInterval(kHeartbeatSchedulerScope,
heartbeat_interval_.InMilliseconds());
bool enabled;
if (settings->GetBoolean(ash::kHeartbeatEnabled, &enabled))
heartbeat_enabled_ = enabled;
if (!heartbeat_enabled_) {
ShutdownGCM();
} else {
ScheduleNextHeartbeat();
}
DVLOG(1) << "heartbeat enabled: " << heartbeat_enabled_;
DVLOG(1) << "heartbeat frequency: " << heartbeat_interval_;
}
void HeartbeatScheduler::ShutdownGCM() {
heartbeat_callback_.Cancel();
registration_helper_.reset();
registration_id_.clear();
if (registered_app_handler_) {
registered_app_handler_ = false;
gcm_driver_->RemoveHeartbeatInterval(kHeartbeatSchedulerScope);
gcm_driver_->RemoveAppHandler(kHeartbeatGCMAppID);
gcm_driver_->RemoveConnectionObserver(this);
}
}
base::TimeDelta HeartbeatScheduler::EnsureValidHeartbeatInterval(
const base::TimeDelta& interval) {
if (interval < kMinHeartbeatInterval) {
DLOG(WARNING) << "Invalid heartbeat interval: " << interval;
return kMinHeartbeatInterval;
}
if (interval > kMaxHeartbeatInterval) {
DLOG(WARNING) << "Invalid heartbeat interval: " << interval;
return kMaxHeartbeatInterval;
}
return interval;
}
void HeartbeatScheduler::ScheduleNextHeartbeat() {
if (!heartbeat_enabled_)
return;
if (registration_id_.empty()) {
if (!registration_helper_) {
registered_app_handler_ = true;
gcm_driver_->AddAppHandler(kHeartbeatGCMAppID, this);
gcm_driver_->AddConnectionObserver(this);
registration_helper_ = std::make_unique<HeartbeatRegistrationHelper>(
gcm_driver_, task_runner_);
registration_helper_->Register(
base::BindOnce(&HeartbeatScheduler::OnRegistrationComplete,
weak_factory_.GetWeakPtr()));
}
return;
}
if (cloud_policy_store_->policy()) {
customer_id_ = cloud_policy_store_->policy()->obfuscated_customer_id();
}
base::TimeDelta delay = std::max(
last_heartbeat_ + heartbeat_interval_ - base::Time::NowFromSystemTime(),
base::TimeDelta());
heartbeat_callback_.Reset(base::BindOnce(&HeartbeatScheduler::SendHeartbeat,
base::Unretained(this)));
task_runner_->PostDelayedTask(FROM_HERE, heartbeat_callback_.callback(),
delay);
}
void HeartbeatScheduler::OnRegistrationComplete(
const std::string& registration_id) {
DCHECK(!registration_id.empty());
registration_helper_.reset();
registration_id_ = registration_id;
if (cloud_policy_client_) {
cloud_policy_client_->UpdateGcmId(
registration_id,
base::BindOnce(&HeartbeatScheduler::OnGcmIdUpdateRequestSent,
weak_factory_.GetWeakPtr()));
SignUpUpstreamNotification();
}
ScheduleNextHeartbeat();
}
void HeartbeatScheduler::SendHeartbeat() {
DCHECK(!registration_id_.empty());
if (!gcm_driver_ || !heartbeat_enabled_ || customer_id_.empty())
return;
gcm::OutgoingMessage message;
message.time_to_live = heartbeat_interval_.InSeconds();
message.id =
base::NumberToString(base::Time::NowFromSystemTime().ToInternalValue());
message.data[kGcmMessageTypeKey] = kHeartbeatTypeValue;
message.data[kHeartbeatTimestampKey] = base::NumberToString(
base::Time::NowFromSystemTime().InMillisecondsSinceUnixEpoch());
message.data[kHeartbeatCustomerIdKey] = customer_id_;
message.data[kHeartbeatDeviceIDKey] = device_id_;
gcm_driver_->Send(kHeartbeatGCMAppID,
GetDestinationID() + kHeartbeatGCMSenderSuffix, message,
base::BindOnce(&HeartbeatScheduler::OnHeartbeatSent,
weak_factory_.GetWeakPtr()));
}
void HeartbeatScheduler::SignUpUpstreamNotification() {
DCHECK(gcm_driver_);
if (registration_id_.empty() || !gcm_driver_->IsConnected())
return;
gcm::OutgoingMessage message;
message.id =
base::NumberToString(base::Time::NowFromSystemTime().ToInternalValue());
message.data[kGcmMessageTypeKey] = kUpstreamNotificationSignUpListeningEvents;
message.data[kUpstreamNotificationNotifyKey] =
GetDestinationID() + kHeartbeatGCMSenderSuffix;
message.data[kUpstreamNotificationRegIdKey] = registration_id_;
gcm_driver_->Send(
kHeartbeatGCMAppID, kUpstreamNotificationSignUpDestinationID, message,
base::BindOnce(&HeartbeatScheduler::OnUpstreamNotificationSent,
weak_factory_.GetWeakPtr()));
}
void HeartbeatScheduler::OnHeartbeatSent(const std::string& message_id,
gcm::GCMClient::Result result) {
DVLOG(1) << "Monitoring heartbeat sent - result = " << result;
DLOG_IF(ERROR, result != gcm::GCMClient::SUCCESS)
<< "Error sending monitoring heartbeat: " << result;
last_heartbeat_ = base::Time::NowFromSystemTime();
ScheduleNextHeartbeat();
}
void HeartbeatScheduler::OnUpstreamNotificationSent(
const std::string& message_id,
gcm::GCMClient::Result result) {
DVLOG(1) << "Upstream notification signup message sent - result = " << result;
DLOG_IF(ERROR, result != gcm::GCMClient::SUCCESS)
<< "Error sending upstream notification signup message: " << result;
}
HeartbeatScheduler::~HeartbeatScheduler() {
ShutdownGCM();
}
void HeartbeatScheduler::ShutdownHandler() {
NOTREACHED() << "HeartbeatScheduler should be destroyed before GCMDriver";
}
void HeartbeatScheduler::OnStoreReset() {
if (!registration_helper_) {
ShutdownGCM();
RefreshHeartbeatSettings();
}
}
void HeartbeatScheduler::OnMessage(const std::string& app_id,
const gcm::IncomingMessage& message) {
NOTREACHED() << "Received incoming message for " << app_id;
}
void HeartbeatScheduler::OnMessagesDeleted(const std::string& app_id) {}
void HeartbeatScheduler::OnSendError(
const std::string& app_id,
const gcm::GCMClient::SendErrorDetails& details) {
}
void HeartbeatScheduler::OnSendAcknowledged(const std::string& app_id,
const std::string& message_id) {
DVLOG(1) << "Heartbeat sent with message_id: " << message_id;
}
void HeartbeatScheduler::OnConnected(const net::IPEndPoint&) {
SignUpUpstreamNotification();
}
void HeartbeatScheduler::OnGcmIdUpdateRequestSent(bool success) {
LOG_IF(WARNING, !success) << "Failed to send GCM id to DM server";
}
}