// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/ash/components/dbus/system_proxy/system_proxy_client.h"
#include <optional>
#include <utility>
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/strcat.h"
#include "base/task/single_thread_task_runner.h"
#include "chromeos/ash/components/dbus/system_proxy/fake_system_proxy_client.h"
#include "dbus/bus.h"
#include "dbus/message.h"
#include "dbus/object_proxy.h"
#include "third_party/cros_system_api/dbus/system_proxy/dbus-constants.h"
namespace ash {
namespace {
SystemProxyClient* g_instance = nullptr;
const char kDbusCallFailure[] = "Failed to call system_proxy.";
const char kProtoMessageParsingFailure[] =
"Failed to parse response message from system_proxy.";
// Tries to parse a proto message from |response| into |proto| and returns null
// if successful. If |response| is nullptr or the message cannot be parsed it
// will return an appropriate error message.
const char* DeserializeProto(dbus::Response* response,
google::protobuf::MessageLite* proto) {
if (!response)
return kDbusCallFailure;
dbus::MessageReader reader(response);
if (!reader.PopArrayOfBytesAsProto(proto))
return kProtoMessageParsingFailure;
return nullptr;
}
void OnSignalConnected(const std::string& interface_name,
const std::string& signal_name,
bool success) {
DCHECK_EQ(interface_name, system_proxy::kSystemProxyInterface);
if (!success) {
LOG(ERROR) << "Failed to connect to the System-proxy d-bus interface.";
}
}
// "Real" implementation of SystemProxyClient talking to the SystemProxy daemon
// on the Chrome OS side.
class SystemProxyClientImpl : public SystemProxyClient {
public:
SystemProxyClientImpl() = default;
SystemProxyClientImpl(const SystemProxyClientImpl&) = delete;
SystemProxyClientImpl& operator=(const SystemProxyClientImpl&) = delete;
~SystemProxyClientImpl() override = default;
// SystemProxyClient overrides:
void SetAuthenticationDetails(
const system_proxy::SetAuthenticationDetailsRequest& request,
SetAuthenticationDetailsCallback callback) override {
CallProtoMethodWithRequest(system_proxy::kSetAuthenticationDetailsMethod,
request, std::move(callback));
}
void ClearUserCredentials(
const system_proxy::ClearUserCredentialsRequest& request,
ClearUserCredentialsCallback callback) override {
CallProtoMethodWithRequest(system_proxy::kClearUserCredentialsMethod,
request, std::move(callback));
}
void ShutDownProcess(const system_proxy::ShutDownRequest& request,
ShutDownProcessCallback callback) override {
CallProtoMethodWithRequest(system_proxy::kShutDownProcessMethod, request,
std::move(callback));
}
void SetWorkerActiveSignalCallback(WorkerActiveCallback callback) override {
DCHECK(callback);
DCHECK(!worker_active_callback_);
worker_active_callback_ = callback;
}
void SetAuthenticationRequiredSignalCallback(
AuthenticationRequiredCallback callback) override {
DCHECK(callback);
DCHECK(!auth_required_callback_);
auth_required_callback_ = callback;
}
void ConnectToWorkerSignals() override {
proxy_->WaitForServiceToBeAvailable(
base::BindOnce(&SystemProxyClientImpl::OnSystemProxyServiceAvailable,
weak_factory_.GetWeakPtr()));
}
void Init(dbus::Bus* bus) {
proxy_ = bus->GetObjectProxy(
system_proxy::kSystemProxyServiceName,
dbus::ObjectPath(system_proxy::kSystemProxyServicePath));
}
private:
TestInterface* GetTestInterface() override { return nullptr; }
// Calls System-proxy's |method_name| method. Once the (asynchronous) call
// finishes, |callback| is called with the response proto (on the same thread
// as this call).
template <class TResponse>
void CallProtoMethod(const char* method_name,
base::OnceCallback<void(const TResponse&)> callback) {
dbus::MethodCall method_call(system_proxy::kSystemProxyInterface,
method_name);
dbus::MessageWriter writer(&method_call);
proxy_->CallMethod(
&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
base::BindOnce(&SystemProxyClientImpl::HandleResponse<TResponse>,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
// Same as |CallProtoMethod| but passes in |request| as input.
template <class TRequest, class TResponse>
void CallProtoMethodWithRequest(
const char* method_name,
const TRequest& request,
base::OnceCallback<void(const TResponse&)> callback) {
dbus::MethodCall method_call(system_proxy::kSystemProxyInterface,
method_name);
dbus::MessageWriter writer(&method_call);
if (!writer.AppendProtoAsArrayOfBytes(request)) {
TResponse response;
response.set_error_message(
base::StrCat({"Failure to call d-bus method: ", method_name}));
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), response));
return;
}
proxy_->CallMethod(
&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
base::BindOnce(&SystemProxyClientImpl::HandleResponse<TResponse>,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
// Parses the response proto message from |response| and calls |callback| with
// the decoded message. Calls |callback| with an |ERROR_DBUS_FAILURE| message
// on error.
template <class TProto>
void HandleResponse(base::OnceCallback<void(const TProto&)> callback,
dbus::Response* response) {
TProto response_proto;
const char* error_message = DeserializeProto(response, &response_proto);
if (error_message) {
response_proto.set_error_message(error_message);
}
std::move(callback).Run(response_proto);
}
void OnWorkerActive(dbus::Signal* signal) {
DCHECK_EQ(signal->GetInterface(), system_proxy::kSystemProxyInterface);
DCHECK_EQ(signal->GetMember(), system_proxy::kWorkerActiveSignal);
dbus::MessageReader signal_reader(signal);
system_proxy::WorkerActiveSignalDetails details;
if (!signal_reader.PopArrayOfBytesAsProto(&details)) {
LOG(ERROR) << "Failed to read connection details for active proxy "
"worker process.";
return;
}
if (!worker_active_callback_) {
LOG(WARNING) << "WorkerActive signal is ignored.";
return;
}
worker_active_callback_.Run(details);
}
void OnAuthenticationRequired(dbus::Signal* signal) {
DCHECK_EQ(signal->GetInterface(), system_proxy::kSystemProxyInterface);
DCHECK_EQ(signal->GetMember(), system_proxy::kAuthenticationRequiredSignal);
dbus::MessageReader signal_reader(signal);
system_proxy::AuthenticationRequiredDetails details;
if (!signal_reader.PopArrayOfBytesAsProto(&details)) {
LOG(ERROR)
<< "Failed to read required authentication details from signal.";
return;
}
if (!auth_required_callback_) {
LOG(WARNING) << "AuthenticationRequired signal is ignored.";
return;
}
auth_required_callback_.Run(details);
}
void OnSystemProxyServiceAvailable(bool is_available) {
if (!is_available) {
LOG(ERROR) << "System-proxy service not available";
return;
}
proxy_->ConnectToSignal(
system_proxy::kSystemProxyInterface, system_proxy::kWorkerActiveSignal,
base::BindRepeating(&SystemProxyClientImpl::OnWorkerActive,
weak_factory_.GetWeakPtr()),
base::BindOnce(&OnSignalConnected));
proxy_->ConnectToSignal(
system_proxy::kSystemProxyInterface,
system_proxy::kAuthenticationRequiredSignal,
base::BindRepeating(&SystemProxyClientImpl::OnAuthenticationRequired,
weak_factory_.GetWeakPtr()),
base::BindOnce(&OnSignalConnected));
}
// Signal callbacks.
WorkerActiveCallback worker_active_callback_;
AuthenticationRequiredCallback auth_required_callback_;
// D-Bus proxy for the SystemProxy daemon, not owned.
raw_ptr<dbus::ObjectProxy> proxy_ = nullptr;
base::WeakPtrFactory<SystemProxyClientImpl> weak_factory_{this};
};
} // namespace
SystemProxyClient::SystemProxyClient() {
CHECK(!g_instance);
g_instance = this;
}
SystemProxyClient::~SystemProxyClient() {
CHECK_EQ(this, g_instance);
g_instance = nullptr;
}
// static
void SystemProxyClient::Initialize(dbus::Bus* bus) {
CHECK(bus);
(new SystemProxyClientImpl())->Init(bus);
}
// static
void SystemProxyClient::InitializeFake() {
new FakeSystemProxyClient();
}
// static
void SystemProxyClient::Shutdown() {
CHECK(g_instance);
delete g_instance;
}
// static
SystemProxyClient* SystemProxyClient::Get() {
return g_instance;
}
} // namespace ash