// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/dbus/vm/vm_launch_service_provider.h"
#include <dbus/dbus-protocol.h>
#include <memory>
#include <sstream>
#include "base/functional/bind.h"
#include "base/logging.h"
#include "chrome/browser/ash/borealis/borealis_app_launcher.h"
#include "chrome/browser/ash/borealis/borealis_features.h"
#include "chrome/browser/ash/borealis/borealis_metrics.h"
#include "chrome/browser/ash/borealis/borealis_service.h"
#include "chrome/browser/ash/borealis/borealis_util.h"
#include "chrome/browser/ash/guest_os/guest_os_launcher.h"
#include "chrome/browser/ash/guest_os/public/guest_os_wayland_server.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chromeos/ash/components/dbus/vm_launch/launch.pb.h"
#include "dbus/message.h"
#include "third_party/cros_system_api/dbus/vm_launch/dbus-constants.h"
namespace ash {
namespace {
void OnExported(const std::string& interface_name,
const std::string& method_name,
bool success) {
LOG_IF(ERROR, !success) << "Failed to export " << interface_name << "."
<< method_name;
}
std::unique_ptr<dbus::Response> AllowStatusToResponse(
borealis::BorealisFeatures::AllowStatus status,
dbus::MethodCall* method_call) {
if (status != borealis::BorealisFeatures::AllowStatus::kAllowed) {
return dbus::ErrorResponse::FromMethodCall(method_call, DBUS_ERROR_FAILED,
"");
}
std::unique_ptr<dbus::Response> response =
dbus::Response::FromMethodCall(method_call);
dbus::MessageWriter writer(response.get());
writer.AppendString("");
return response;
}
void OnAllowChecked(Profile* profile,
dbus::MethodCall* method_call,
dbus::ExportedObject::ResponseSender response_sender,
bool launch,
borealis::BorealisFeatures::AllowStatus new_allowed) {
if (launch) {
// When requested, setting the correct token should have the effect of
// running the client app, which will bring up the installer or launch the
// client as needed.
borealis::BorealisService::GetForProfile(profile)->AppLauncher().Launch(
borealis::kClientAppId, borealis::BorealisLaunchSource::kInsertCoin,
base::DoNothing());
}
std::move(response_sender)
.Run(AllowStatusToResponse(new_allowed, method_call));
}
template <typename T>
void HandleReturn(dbus::MethodCall* method_call,
dbus::ExportedObject::ResponseSender response_sender,
base::expected<T, std::string> response) {
if (!response.has_value()) {
std::move(response_sender)
.Run(dbus::ErrorResponse::FromMethodCall(method_call, DBUS_ERROR_FAILED,
response.error()));
return;
}
std::unique_ptr<dbus::Response> dbus_response =
dbus::Response::FromMethodCall(method_call);
dbus::MessageWriter writer(dbus_response.get());
writer.AppendProtoAsArrayOfBytes(response.value());
std::move(response_sender).Run(std::move(dbus_response));
}
} // namespace
VmLaunchServiceProvider::VmLaunchServiceProvider() = default;
VmLaunchServiceProvider::~VmLaunchServiceProvider() = default;
void VmLaunchServiceProvider::Start(
scoped_refptr<dbus::ExportedObject> exported_object) {
exported_object->ExportMethod(
vm_tools::launch::kVmLaunchServiceInterface,
vm_tools::launch::kVmLaunchServiceProvideVmTokenMethod,
base::BindRepeating(&VmLaunchServiceProvider::ProvideVmToken,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&OnExported));
exported_object->ExportMethod(
vm_tools::launch::kVmLaunchServiceInterface,
vm_tools::launch::kVmLaunchServiceEnsureVmLaunchedMethod,
base::BindRepeating(&VmLaunchServiceProvider::EnsureVmLaunched,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&OnExported));
}
void VmLaunchServiceProvider::ProvideVmToken(
dbus::MethodCall* method_call,
dbus::ExportedObject::ResponseSender response_sender) {
std::string token;
dbus::MessageReader reader(method_call);
if (!reader.PopString(&token)) {
std::move(response_sender)
.Run(dbus::ErrorResponse::FromMethodCall(
method_call, DBUS_ERROR_INVALID_ARGS, "Token not provided"));
return;
}
bool launch;
if (!reader.PopBool(&launch)) {
launch = true;
}
Profile* profile = ProfileManager::GetPrimaryUserProfile();
if (!profile) {
std::move(response_sender)
.Run(dbus::ErrorResponse::FromMethodCall(
method_call, DBUS_ERROR_FAILED,
"Unable to determine primary profile"));
return;
}
// TODO(b/317157600): Tokens are no longer required so we have the option to
// remove this dbus method entirely.
borealis::BorealisService::GetForProfile(profile)->Features().IsAllowed(
base::BindOnce(&OnAllowChecked, profile, method_call,
std::move(response_sender), launch));
}
void VmLaunchServiceProvider::EnsureVmLaunched(
dbus::MethodCall* method_call,
dbus::ExportedObject::ResponseSender response_sender) {
vm_tools::launch::EnsureVmLaunchedRequest request;
if (!dbus::MessageReader(method_call).PopArrayOfBytesAsProto(&request)) {
std::move(response_sender)
.Run(dbus::ErrorResponse::FromMethodCall(
method_call, DBUS_ERROR_INVALID_ARGS,
"Unable to parse EnsureVmLaunchedRequest from message"));
return;
}
guest_os::launcher::EnsureLaunched(
request,
base::BindOnce(&HandleReturn<vm_tools::launch::EnsureVmLaunchedResponse>,
method_call, std::move(response_sender)));
}
} // namespace ash