// 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.
#ifndef CHROMECAST_MOJO_INTERFACE_BUNDLE_H_
#define CHROMECAST_MOJO_INTERFACE_BUNDLE_H_
#include <string>
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/sequence_checker.h"
#include "base/task/sequenced_task_runner.h"
#include "chromecast/mojo/binder_factory.h"
#include "chromecast/mojo/mojom/remote_interfaces.mojom.h"
#include "mojo/public/cpp/bindings/generic_pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "mojo/public/cpp/bindings/remote.h"
namespace chromecast {
// This class essentially combines RemoteInterfaces and BinderRegistry into
// one. On the host side, this class can be created and used like a
// BinderRegistry. InterfaceBundle can also dispense RemoteInterfaces remotes
// for clients to invoke local binders.
//
// This class should only be used on one sequence. Local interfaces will be
// bound to the sequence that the InterfaceBundle is created on, unless a task
// runner is provided in AddBinder().
//
// Implementations that are added via raw pointer (instead of a binder callback)
// will have the Receiver owned by the InterfaceBundle. When the InterfaceBundle
// is destroyed, the connection is severed. All implementations that are added
// by raw pointer must therefore outlive the InterfaceBundle.
//
// =============================================================================
// Example Usage
// =============================================================================
//
// Add implementations to the bundle, no Binding boilerplate required:
//
// InterfaceBundle bundle;
// bundle.AddInterface<mojom::Foo>(GetFooImpl());
// bundle.AddInterface<mojom::Bar>(GetBarImpl());
//
// Dispense a RemoteInterfaces, which can be used by clients:
//
// mojo::Remote<mojom::RemoteInterfaces> provider(bundle.CreateRemote());
// mojo::Remote<mojom::Bar> bar;
// provider->BindNewPipe(&bar);
// bar->DoBarStuff();
class InterfaceBundle final : private mojom::RemoteInterfaces {
public:
// Specifies the number of expected clients for a given Receiver.
enum ReceiverType {
MULTIPLE_BINDERS, // Multiple clients, use a ReceiverSet.
SINGLE_BINDER, // Single client, use a Receiver.
};
InterfaceBundle();
InterfaceBundle(const InterfaceBundle&) = delete;
~InterfaceBundle() override;
InterfaceBundle& operator=(const InterfaceBundle&) = delete;
// Adds an implementation for an interface of type <Interface>. When the
// interface is requested via one of the consumer methods below, |interface|
// will receive the method calls.
//
// |interface| *must* outlive the InterfaceBundle, or else mojo method calls
// could be invoked on a destroyed object.
template <typename Interface>
bool AddInterface(Interface* interface,
ReceiverType receiver_type = MULTIPLE_BINDERS) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (local_interfaces_.HasInterface<Interface>()) {
LOG(DFATAL) << "Local interface '" << Interface::Name_ << "' has already "
<< "been added to this bundle.";
return false;
}
if (receiver_type == MULTIPLE_BINDERS) {
local_interfaces_.AddInterface<Interface>(interface);
} else {
local_interfaces_.AddSingleBinderInterface<Interface>(interface);
}
return true;
}
// Similar to BinderRegistry::AddInterface(), AddBinder() allows clients to
// provide their own binder callbacks and task runners. If |task_runner| is
// provided, then |callback| will be invoked on |task_runner| for every
// incoming request to <Interface>. Subsequent calls to <Interface>'s methods
// will post to |task_runner|. If |task_runner| is not provided, the current
// SequencedTaskRunner will receive incoming bind requests and method calls.
template <typename Interface>
bool AddBinder(
const base::RepeatingCallback<void(mojo::PendingReceiver<Interface>)>&
callback,
scoped_refptr<base::SequencedTaskRunner> task_runner = nullptr) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (local_interfaces_.HasInterface<Interface>()) {
LOG(DFATAL) << "Local interface '" << Interface::Name_ << "' has already "
<< "been added to this bundle.";
return false;
}
local_interfaces_.AddBinder<Interface>(callback, task_runner);
return true;
}
template <typename Interface>
void RemoveInterface() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
local_interfaces_.RemoveInterface<Interface>();
}
// Creates a remote reference which can be passed over IPC to a remote client.
mojo::PendingRemote<mojom::RemoteInterfaces> CreateRemote();
// Severs all client connections. This should only be called on teardown.
void Close();
// mojom::RemoteInterfaces implementation:
void BindInterface(const std::string& interface_name,
mojo::ScopedMessagePipeHandle handle) override;
void AddClient(
mojo::PendingReceiver<mojom::RemoteInterfaces> receiver) override;
// Attempt to bind a generic receiver. Succeeds if there is an available
// implementation or binder callback registered.
bool TryBindReceiver(mojo::GenericPendingReceiver& receiver) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (local_interfaces_.HasInterface(*receiver.interface_name())) {
std::string interface_name = *receiver.interface_name();
local_interfaces_.Bind(interface_name, receiver.PassPipe());
return true;
}
return false;
}
private:
// For interfaces that are provided as a local pointer without any binding
// logic, we can use MultiBinderFactory to expose a binding surface.
MultiBinderFactory local_interfaces_;
mojo::ReceiverSet<mojom::RemoteInterfaces> client_receivers_;
SEQUENCE_CHECKER(sequence_checker_);
};
} // namespace chromecast
#endif // CHROMECAST_MOJO_INTERFACE_BUNDLE_H_