blob: b1c3dd6bf2eb4cf0d84f8fb82533ddf8bc8a1426 [file] [log] [blame]
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "diagnostics/wilco_dtc_supportd/core.h"
#include <algorithm>
#include <cstddef>
#include <utility>
#include <base/barrier_closure.h>
#include <base/bind.h>
#include <base/logging.h>
#include <base/optional.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <base/threading/thread_task_runner_handle.h>
#include "diagnostics/wilco_dtc_supportd/grpc_client_manager.h"
#include "diagnostics/wilco_dtc_supportd/mojo_service.h"
#include "diagnostics/wilco_dtc_supportd/mojo_service_factory.h"
#include "diagnostics/wilco_dtc_supportd/probe_service_impl.h"
#include "mojo/cros_healthd_probe.mojom.h"
namespace diagnostics {
namespace {
using EcEvent = EcService::EcEvent;
using EcEventReason = EcService::EcEvent::Reason;
using MojomWilcoDtcSupportdWebRequestStatus =
chromeos::wilco_dtc_supportd::mojom::WilcoDtcSupportdWebRequestStatus;
using MojomWilcoDtcSupportdWebRequestHttpMethod =
chromeos::wilco_dtc_supportd::mojom::WilcoDtcSupportdWebRequestHttpMethod;
// Converts HTTP method into an appropriate mojom one.
bool ConvertWebRequestHttpMethodToMojom(
Core::WebRequestHttpMethod http_method,
MojomWilcoDtcSupportdWebRequestHttpMethod* mojo_http_method_out) {
DCHECK(mojo_http_method_out);
switch (http_method) {
case Core::WebRequestHttpMethod::kGet:
*mojo_http_method_out = MojomWilcoDtcSupportdWebRequestHttpMethod::kGet;
return true;
case Core::WebRequestHttpMethod::kHead:
*mojo_http_method_out = MojomWilcoDtcSupportdWebRequestHttpMethod::kHead;
return true;
case Core::WebRequestHttpMethod::kPost:
*mojo_http_method_out = MojomWilcoDtcSupportdWebRequestHttpMethod::kPost;
return true;
case Core::WebRequestHttpMethod::kPut:
*mojo_http_method_out = MojomWilcoDtcSupportdWebRequestHttpMethod::kPut;
return true;
case Core::WebRequestHttpMethod::kPatch:
*mojo_http_method_out = MojomWilcoDtcSupportdWebRequestHttpMethod::kPatch;
return true;
}
return false;
}
// Convert the result back from mojom status.
bool ConvertStatusFromMojom(MojomWilcoDtcSupportdWebRequestStatus mojo_status,
Core::WebRequestStatus* status_out) {
DCHECK(status_out);
switch (mojo_status) {
case MojomWilcoDtcSupportdWebRequestStatus::kOk:
*status_out = Core::WebRequestStatus::kOk;
return true;
case MojomWilcoDtcSupportdWebRequestStatus::kNetworkError:
*status_out = Core::WebRequestStatus::kNetworkError;
return true;
case MojomWilcoDtcSupportdWebRequestStatus::kHttpError:
*status_out = Core::WebRequestStatus::kHttpError;
return true;
}
return false;
}
bool ConvertPowerEventToGrpc(
PowerdEventService::Observer::PowerEventType type,
grpc_api::HandlePowerNotificationRequest::PowerEvent* type_out) {
DCHECK(type_out);
switch (type) {
case PowerdEventService::Observer::PowerEventType::kAcInsert:
*type_out = grpc_api::HandlePowerNotificationRequest::AC_INSERT;
return true;
case PowerdEventService::Observer::PowerEventType::kAcRemove:
*type_out = grpc_api::HandlePowerNotificationRequest::AC_REMOVE;
return true;
case PowerdEventService::Observer::PowerEventType::kOsSuspend:
*type_out = grpc_api::HandlePowerNotificationRequest::OS_SUSPEND;
return true;
case PowerdEventService::Observer::PowerEventType::kOsResume:
*type_out = grpc_api::HandlePowerNotificationRequest::OS_RESUME;
return true;
}
return false;
}
} // namespace
Core::Core(Delegate* delegate,
const GrpcClientManager* grpc_client_manager,
const std::vector<std::string>& grpc_service_uris,
MojoServiceFactory* mojo_service_factory)
: delegate_(delegate),
grpc_client_manager_(grpc_client_manager),
grpc_service_uris_(grpc_service_uris),
grpc_server_(base::ThreadTaskRunnerHandle::Get(), grpc_service_uris_),
mojo_service_factory_(mojo_service_factory) {
DCHECK(delegate);
DCHECK(grpc_client_manager_);
DCHECK(mojo_service_factory_);
ec_service_ = delegate_->CreateEcService();
probe_service_ = delegate->CreateProbeService(this);
DCHECK(ec_service_);
ec_service_->AddObserver(this);
}
Core::~Core() = default;
bool Core::Start() {
// Associate RPCs of the to-be-exposed gRPC interface with methods of
// |grpc_service_|.
grpc_server_.RegisterHandler(
&grpc_api::WilcoDtcSupportd::AsyncService::RequestSendMessageToUi,
base::Bind(&GrpcService::SendMessageToUi,
base::Unretained(&grpc_service_)));
grpc_server_.RegisterHandler(
&grpc_api::WilcoDtcSupportd::AsyncService::RequestGetProcData,
base::Bind(&GrpcService::GetProcData, base::Unretained(&grpc_service_)));
grpc_server_.RegisterHandler(
&grpc_api::WilcoDtcSupportd::AsyncService::RequestGetSysfsData,
base::Bind(&GrpcService::GetSysfsData, base::Unretained(&grpc_service_)));
grpc_server_.RegisterHandler(
&grpc_api::WilcoDtcSupportd::AsyncService::RequestGetEcTelemetry,
base::Bind(&GrpcService::GetEcTelemetry,
base::Unretained(&grpc_service_)));
grpc_server_.RegisterHandler(
&grpc_api::WilcoDtcSupportd::AsyncService::RequestPerformWebRequest,
base::Bind(&GrpcService::PerformWebRequest,
base::Unretained(&grpc_service_)));
grpc_server_.RegisterHandler(
&grpc_api::WilcoDtcSupportd::AsyncService::RequestGetAvailableRoutines,
base::Bind(&GrpcService::GetAvailableRoutines,
base::Unretained(&grpc_service_)));
grpc_server_.RegisterHandler(
&grpc_api::WilcoDtcSupportd::AsyncService::RequestRunRoutine,
base::Bind(&GrpcService::RunRoutine, base::Unretained(&grpc_service_)));
grpc_server_.RegisterHandler(
&grpc_api::WilcoDtcSupportd::AsyncService::RequestGetRoutineUpdate,
base::Bind(&GrpcService::GetRoutineUpdate,
base::Unretained(&grpc_service_)));
grpc_server_.RegisterHandler(
&grpc_api::WilcoDtcSupportd::AsyncService::RequestGetOsVersion,
base::Bind(&GrpcService::GetOsVersion, base::Unretained(&grpc_service_)));
grpc_server_.RegisterHandler(
&grpc_api::WilcoDtcSupportd::AsyncService::RequestGetVpdField,
base::Bind(&GrpcService::GetVpdField, base::Unretained(&grpc_service_)));
grpc_server_.RegisterHandler(
&grpc_api::WilcoDtcSupportd::AsyncService::RequestGetConfigurationData,
base::Bind(&GrpcService::GetConfigurationData,
base::Unretained(&grpc_service_)));
grpc_server_.RegisterHandler(
&grpc_api::WilcoDtcSupportd::AsyncService::RequestGetDriveSystemData,
base::Bind(&GrpcService::GetDriveSystemData,
base::Unretained(&grpc_service_)));
grpc_server_.RegisterHandler(
&grpc_api::WilcoDtcSupportd::AsyncService::
RequestRequestBluetoothDataNotification,
base::Bind(&GrpcService::RequestBluetoothDataNotification,
base::Unretained(&grpc_service_)));
grpc_server_.RegisterHandler(
&grpc_api::WilcoDtcSupportd::AsyncService::
RequestGetStatefulPartitionAvailableCapacity,
base::Bind(&GrpcService::GetStatefulPartitionAvailableCapacity,
base::Unretained(&grpc_service_)));
// Start the gRPC server that listens for incoming gRPC requests.
VLOG(1) << "Starting gRPC server";
if (!grpc_server_.Start()) {
LOG(ERROR) << "Failed to start the gRPC server listening on: "
<< base::JoinString(grpc_service_uris_, ", ");
return false;
}
VLOG(0) << "Successfully started gRPC server listening on "
<< base::JoinString(grpc_service_uris_, ",");
// Start EC event service.
if (!ec_service_->Start()) {
LOG(WARNING)
<< "Failed to start EC event service. EC events will be ignored.";
}
return true;
}
void Core::ShutDown(base::OnceClosure on_shutdown_callback) {
VLOG(1) << "Tearing down gRPC server, gRPC wilco_dtc clients, "
"EC event service and D-Bus server";
UnsubscribeFromEventServices();
const base::Closure barrier_closure =
base::BarrierClosure(2, std::move(on_shutdown_callback));
ec_service_->ShutDown(barrier_closure);
grpc_server_.ShutDown(barrier_closure);
}
void Core::CreateDbusAdapters(const scoped_refptr<dbus::Bus>& bus) {
DCHECK(bus);
bluetooth_client_ = delegate_->CreateBluetoothClient(bus);
DCHECK(bluetooth_client_);
debugd_adapter_ = delegate_->CreateDebugdAdapter(bus);
DCHECK(debugd_adapter_);
powerd_adapter_ = delegate_->CreatePowerdAdapter(bus);
DCHECK(powerd_adapter_);
bluetooth_event_service_ =
delegate_->CreateBluetoothEventService(bluetooth_client_.get());
DCHECK(bluetooth_event_service_);
bluetooth_event_service_->AddObserver(this);
powerd_event_service_ =
delegate_->CreatePowerdEventService(powerd_adapter_.get());
DCHECK(powerd_event_service_);
powerd_event_service_->AddObserver(this);
}
bool Core::GetCrosHealthdDiagnosticsService(
chromeos::cros_healthd::mojom::CrosHealthdDiagnosticsServiceRequest
service) {
MojoService* mojo_service = mojo_service_factory_->Get();
if (!mojo_service) {
LOG(WARNING) << "GetCrosHealthdDiagnosticsService happens before Mojo "
<< "connection is established.";
return false;
}
mojo_service->GetCrosHealthdDiagnosticsService(std::move(service));
return true;
}
bool Core::BindCrosHealthdProbeService(
chromeos::cros_healthd::mojom::CrosHealthdProbeServiceRequest service) {
MojoService* mojo_service = mojo_service_factory_->Get();
if (!mojo_service) {
LOG(WARNING) << "BindCrosHealthdProbeService happens before Mojo "
<< "connection is established.";
return false;
}
mojo_service->GetCrosHealthdProbeService(std::move(service));
return true;
}
void Core::SendWilcoDtcMessageToUi(const std::string& json_message,
const SendMessageToUiCallback& callback) {
VLOG(1) << "SendWilcoDtcMessageToUi() json_message=" << json_message;
MojoService* mojo_service = mojo_service_factory_->Get();
if (!mojo_service) {
constexpr char kErrMsg[] =
"GetConfigurationDataFromBrowser happens before "
"Mojo connection is established.";
LOG(WARNING) << kErrMsg;
callback.Run(grpc::Status(grpc::StatusCode::UNKNOWN, kErrMsg), "");
return;
}
mojo_service->SendWilcoDtcMessageToUi(json_message, callback);
}
void Core::PerformWebRequestToBrowser(
WebRequestHttpMethod http_method,
const std::string& url,
const std::vector<std::string>& headers,
const std::string& request_body,
const PerformWebRequestToBrowserCallback& callback) {
VLOG(1) << "Core::PerformWebRequestToBrowser";
MojoService* mojo_service = mojo_service_factory_->Get();
if (!mojo_service) {
LOG(WARNING) << "PerformWebRequestToBrowser happens before Mojo connection "
<< "is established.";
callback.Run(WebRequestStatus::kInternalError, 0 /* http_status */,
"" /* response_body */);
return;
}
MojomWilcoDtcSupportdWebRequestHttpMethod mojo_http_method;
if (!ConvertWebRequestHttpMethodToMojom(http_method, &mojo_http_method)) {
LOG(ERROR) << "Unknown gRPC http method: " << static_cast<int>(http_method);
callback.Run(WebRequestStatus::kInternalError, 0 /* http_status */,
"" /* response_body */);
return;
}
mojo_service->PerformWebRequest(
mojo_http_method, url, headers, request_body,
base::Bind(
[](const PerformWebRequestToBrowserCallback& callback,
MojomWilcoDtcSupportdWebRequestStatus mojo_status, int http_status,
base::StringPiece response_body) {
WebRequestStatus status;
if (!ConvertStatusFromMojom(mojo_status, &status)) {
LOG(ERROR) << "Unknown mojo web request status: " << mojo_status;
callback.Run(WebRequestStatus::kInternalError,
0 /* http_status */, "" /* response_body */);
return;
}
callback.Run(status, http_status, response_body);
},
callback));
}
void Core::GetAvailableRoutinesToService(
const GetAvailableRoutinesToServiceCallback& callback) {
routine_service_.GetAvailableRoutines(callback);
}
void Core::RunRoutineToService(const grpc_api::RunRoutineRequest& request,
const RunRoutineToServiceCallback& callback) {
routine_service_.RunRoutine(request, callback);
}
void Core::GetRoutineUpdateRequestToService(
int uuid,
grpc_api::GetRoutineUpdateRequest::Command command,
bool include_output,
const GetRoutineUpdateRequestToServiceCallback& callback) {
routine_service_.GetRoutineUpdate(uuid, command, include_output, callback);
}
void Core::GetConfigurationDataFromBrowser(
const GetConfigurationDataFromBrowserCallback& callback) {
VLOG(1) << "Core::GetConfigurationDataFromBrowser";
MojoService* mojo_service = mojo_service_factory_->Get();
if (!mojo_service) {
LOG(WARNING) << "GetConfigurationDataFromBrowser happens before Mojo "
<< "connection is established.";
callback.Run("" /* json_configuration_data */);
return;
}
mojo_service->GetConfigurationData(callback);
}
void Core::GetDriveSystemData(DriveSystemDataType data_type,
const GetDriveSystemDataCallback& callback) {
if (!debugd_adapter_) {
LOG(WARNING) << "DebugdAdapter is not yet ready for incoming requests";
callback.Run("", false /* success */);
return;
}
auto result_callback = base::Bind(
[](const GetDriveSystemDataCallback& callback, const std::string& result,
brillo::Error* error) {
if (error) {
LOG(WARNING) << "Debugd smartctl failed with error: "
<< error->GetMessage();
callback.Run("", false /* success */);
return;
}
callback.Run(result, true /* success */);
},
callback);
switch (data_type) {
case DriveSystemDataType::kSmartAttributes:
debugd_adapter_->GetSmartAttributes(result_callback);
break;
case DriveSystemDataType::kIdentityAttributes:
debugd_adapter_->GetNvmeIdentity(result_callback);
break;
}
}
void Core::RequestBluetoothDataNotification() {
VLOG(1) << "WilcoDtcSupportdCore::RequestBluetoothDataNotification";
if (!bluetooth_event_service_) {
VLOG(1) << "Bluetooth event service not yet ready";
return;
}
NotifyClientsBluetoothAdapterState(
bluetooth_event_service_->GetLatestEvent());
}
void Core::ProbeTelemetryInfo(
std::vector<chromeos::cros_healthd::mojom::ProbeCategoryEnum> categories,
ProbeTelemetryInfoCallback callback) {
VLOG(1) << "Core::ProbeTelemetryInfo";
probe_service_->ProbeTelemetryInfo(std::move(categories),
std::move(callback));
}
EcService* Core::GetEcService() {
DCHECK(ec_service_);
return ec_service_.get();
}
void Core::BluetoothAdapterDataChanged(
const std::vector<BluetoothEventService::AdapterData>& adapters) {
VLOG(1) << "Core::BluetoothAdapterDataChanged";
NotifyClientsBluetoothAdapterState(adapters);
}
void Core::OnPowerdEvent(PowerEventType type) {
VLOG(1) << "Core::OnPowerdEvent: " << static_cast<int>(type);
grpc_api::HandlePowerNotificationRequest::PowerEvent grpc_type;
if (!ConvertPowerEventToGrpc(type, &grpc_type)) {
LOG(ERROR) << "Unable to convert power event to gRPC power event: "
<< static_cast<int>(type);
return;
}
grpc_api::HandlePowerNotificationRequest request;
request.set_power_event(grpc_type);
for (auto& client : grpc_client_manager_->GetClients()) {
client->CallRpc(
&grpc_api::WilcoDtc::Stub::AsyncHandlePowerNotification, request,
base::Bind([](grpc::Status status,
std::unique_ptr<grpc_api::HandlePowerNotificationResponse>
response) {
if (!status.ok()) {
VLOG(1) << "Failed to call HandlePowerNotification gRPC "
"method on wilco_dtc. grpc error code: "
<< status.error_code()
<< ", error message: " << status.error_message();
return;
}
VLOG(1) << "gRPC method HandlePowerNotification was "
"successfully called on wilco_dtc";
}));
}
}
void Core::OnEcEvent(const EcEvent& ec_event) {
VLOG(1) << "Core::OnEcEvent: type=" << static_cast<int>(ec_event.type)
<< " reason=" << static_cast<int>(ec_event.GetReason());
SendGrpcEcEventToWilcoDtc(ec_event);
// Parse EcEventReason into a MojoEvent and forward to the delegate.
// We only will forward certain events. If they aren't relevant, ignore.
switch (ec_event.GetReason()) {
case EcEventReason::kNonWilcoCharger:
SendMojoEcEventToBrowser(MojoEvent::kNonWilcoCharger);
break;
case EcEventReason::kLowPowerCharger:
SendMojoEcEventToBrowser(MojoEvent::kLowPowerCharger);
break;
case EcEventReason::kBatteryAuth:
SendMojoEcEventToBrowser(MojoEvent::kBatteryAuth);
break;
case EcEventReason::kDockDisplay:
SendMojoEcEventToBrowser(MojoEvent::kDockDisplay);
break;
case EcEventReason::kDockThunderbolt:
SendMojoEcEventToBrowser(MojoEvent::kDockThunderbolt);
break;
case EcEventReason::kIncompatibleDock:
SendMojoEcEventToBrowser(MojoEvent::kIncompatibleDock);
break;
case EcEventReason::kDockError:
SendMojoEcEventToBrowser(MojoEvent::kDockError);
break;
case EcEventReason::kSysNotification:
VLOG(2) << "Received EC event that doesn't trigger a mojo event";
break;
case EcEventReason::kNonSysNotification:
VLOG(2) << "Received a non-system notification EC event";
break;
}
}
void Core::SendGrpcEcEventToWilcoDtc(const EcEvent& ec_event) {
VLOG(1) << "Core::SendGrpcEcEventToWilcoDtc";
size_t payload_size = ec_event.PayloadSizeInBytes();
if (payload_size > sizeof(ec_event.payload)) {
VLOG(2) << "Received EC event with invalid payload size: " << payload_size;
return;
}
grpc_api::HandleEcNotificationRequest request;
request.set_type(ec_event.type);
request.set_payload(&ec_event.payload, payload_size);
for (auto& client : grpc_client_manager_->GetClients()) {
client->CallRpc(
&grpc_api::WilcoDtc::Stub::AsyncHandleEcNotification, request,
base::Bind([](grpc::Status status,
std::unique_ptr<grpc_api::HandleEcNotificationResponse>
response) {
if (!status.ok()) {
VLOG(1)
<< "Failed to call HandleEcNotificationRequest gRPC method on "
"wilco_dtc. grpc error code: "
<< status.error_code()
<< ", error message: " << status.error_message();
return;
}
VLOG(1) << "gRPC method HandleEcNotificationRequest was successfully"
"called on wilco_dtc";
}));
}
}
void Core::SendMojoEcEventToBrowser(const MojoEvent& mojo_event) {
VLOG(1) << "Core::HandleEvent";
MojoService* mojo_service = mojo_service_factory_->Get();
if (!mojo_service) {
LOG(WARNING) << "SendMojoEcEventToBrowser happens before Mojo connection "
"is established.";
return;
}
mojo_service->HandleEvent(mojo_event);
}
void Core::NotifyClientsBluetoothAdapterState(
const std::vector<BluetoothEventService::AdapterData>& adapters) {
grpc_api::HandleBluetoothDataChangedRequest request;
for (const auto& adapter : adapters) {
VLOG(1) << base::StringPrintf(
"Bluetooth adapter adapter: name=%s addres=%s powered=%d "
"connected_devices_count=%d",
adapter.name.c_str(), adapter.address.c_str(), adapter.powered,
adapter.connected_devices_count);
auto adapter_data = request.add_adapters();
adapter_data->set_adapter_name(adapter.name);
adapter_data->set_adapter_mac_address(adapter.address);
adapter_data->set_connected_devices_count(adapter.connected_devices_count);
if (adapter.powered) {
adapter_data->set_carrier_status(
grpc_api::HandleBluetoothDataChangedRequest::AdapterData::STATUS_UP);
} else {
adapter_data->set_carrier_status(
grpc_api::HandleBluetoothDataChangedRequest::AdapterData::
STATUS_DOWN);
}
}
for (auto& client : grpc_client_manager_->GetClients()) {
client->CallRpc(
&grpc_api::WilcoDtc::Stub::AsyncHandleBluetoothDataChanged, request,
base::Bind(
[](grpc::Status status,
std::unique_ptr<grpc_api::HandleBluetoothDataChangedResponse>
response) {
if (!status.ok()) {
VLOG(1) << "Failed to call HandleBluetoothDataChanged gRPC "
"method on wilco_dtc. grpc error code: "
<< status.error_code()
<< ", error message: " << status.error_message();
return;
}
VLOG(1) << "gRPC method HandleBluetoothDataChanged was "
"successfully called on wilco_dtc";
}));
}
}
void Core::UnsubscribeFromEventServices() {
if (bluetooth_event_service_) {
bluetooth_event_service_->RemoveObserver(this);
}
if (powerd_event_service_) {
powerd_event_service_->RemoveObserver(this);
}
ec_service_->RemoveObserver(this);
}
} // namespace diagnostics