chromium/chromeos/ash/services/federated/public/cpp/service_connection.cc

// 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 "chromeos/ash/services/federated/public/cpp/service_connection.h"

#include "base/component_export.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/no_destructor.h"
#include "base/sequence_checker.h"
#include "chromeos/ash/components/dbus/federated/federated_client.h"
#include "chromeos/ash/services/federated/public/mojom/federated_service.mojom.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/platform/platform_channel.h"
#include "mojo/public/cpp/system/invitation.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

namespace ash::federated {

namespace {

ServiceConnection* g_fake_service_connection_for_testing = nullptr;

// Real Impl of ServiceConnection
class ServiceConnectionImpl : public ServiceConnection {
 public:
  ServiceConnectionImpl();
  ServiceConnectionImpl(const ServiceConnectionImpl&) = delete;
  ServiceConnectionImpl& operator=(const ServiceConnectionImpl&) = delete;
  ~ServiceConnectionImpl() override = default;

  // ServiceConnection:
  void BindReceiver(
      mojo::PendingReceiver<chromeos::federated::mojom::FederatedService>
          receiver) override;

 private:
  // Binds the top level interface |federated_service_| to an
  // implementation in the Federated Service daemon, if it is not already bound.
  // The binding is accomplished via D-Bus bootstrap.
  void BindFederatedServiceIfNeeded();

  // Mojo disconnect handler. Resets |federated_service_|, which
  // will be reconnected upon next use.
  void OnMojoDisconnect();

  // Response callback for FederatedClient::BootstrapMojoConnection.
  void OnBootstrapMojoConnectionResponse(bool success);

  mojo::Remote<chromeos::federated::mojom::FederatedService> federated_service_;

  SEQUENCE_CHECKER(sequence_checker_);
};

ServiceConnectionImpl::ServiceConnectionImpl() {
  DETACH_FROM_SEQUENCE(sequence_checker_);
}

void ServiceConnectionImpl::BindReceiver(
    mojo::PendingReceiver<chromeos::federated::mojom::FederatedService>
        receiver) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  BindFederatedServiceIfNeeded();
  federated_service_->Clone(std::move(receiver));
}

void ServiceConnectionImpl::BindFederatedServiceIfNeeded() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (federated_service_) {
    return;
  }

  mojo::PlatformChannel platform_channel;

  // Prepare a Mojo invitation to send through |platform_channel|.
  mojo::OutgoingInvitation invitation;
  // Include an initial Mojo pipe in the invitation.
  mojo::ScopedMessagePipeHandle pipe = invitation.AttachMessagePipe(
      ::federated::kBootstrapMojoConnectionChannelToken);
  mojo::OutgoingInvitation::Send(std::move(invitation),
                                 base::kNullProcessHandle,
                                 platform_channel.TakeLocalEndpoint());

  // Bind our end of |pipe| to our mojo::Remote<FederatedService>. The daemon
  // should bind its end to a FederatedService implementation.
  federated_service_.Bind(
      mojo::PendingRemote<chromeos::federated::mojom::FederatedService>(
          std::move(pipe), 0u /* version */));
  federated_service_.set_disconnect_handler(base::BindOnce(
      &ServiceConnectionImpl::OnMojoDisconnect, base::Unretained(this)));

  // Send the file descriptor for the other end of |platform_channel| to the
  // Federated service daemon over D-Bus.
  FederatedClient::Get()->BootstrapMojoConnection(
      platform_channel.TakeRemoteEndpoint().TakePlatformHandle().TakeFD(),
      base::BindOnce(&ServiceConnectionImpl::OnBootstrapMojoConnectionResponse,
                     base::Unretained(this)));
}

void ServiceConnectionImpl::OnMojoDisconnect() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // Connection errors are not expected so log a warning.
  LOG(WARNING) << "Federated Service Mojo connection closed";
  federated_service_.reset();
}

void ServiceConnectionImpl::OnBootstrapMojoConnectionResponse(
    const bool success) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!success) {
    LOG(WARNING) << "BootstrapMojoConnection D-Bus call failed";
    federated_service_.reset();
  }
}

}  // namespace

ServiceConnection* ServiceConnection::GetInstance() {
  if (g_fake_service_connection_for_testing) {
    return g_fake_service_connection_for_testing;
  }
  static base::NoDestructor<ServiceConnectionImpl> service_connection;
  return service_connection.get();
}

ScopedFakeServiceConnectionForTest::ScopedFakeServiceConnectionForTest(
    ServiceConnection* fake_service_connection) {
  DCHECK(!g_fake_service_connection_for_testing);
  g_fake_service_connection_for_testing = fake_service_connection;
}

ScopedFakeServiceConnectionForTest::~ScopedFakeServiceConnectionForTest() {
  g_fake_service_connection_for_testing = nullptr;
}

}  // namespace ash::federated