// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "mojo/public/cpp/platform/binder_exchange.h"
#include <array>
#include <optional>
#include <utility>
#include "base/android/binder.h"
#include "base/memory/ref_counted.h"
#include "base/synchronization/lock.h"
#include "base/types/expected.h"
#include "base/types/expected_macros.h"
#include "third_party/abseil-cpp/absl/types/variant.h"
namespace mojo {
namespace {
// Binder class used to identify ExchangeReceiver binders.
DEFINE_BINDER_CLASS(ExchangeInterface);
// Binder class used to identify PeerConnector binders.
DEFINE_BINDER_CLASS(PeerConnectorInterface);
// Expects a single transaction containing an arbitrary endpoint binder, and
// forwards it to a callback. An instance of this is created internally at each
// endpoint performing a binder exchange. Its only job is to receive a peer
// endpoint from the shared Exchange instance and pass it along to the local
// endpoint, completing one side of the exchange.
class PeerConnector
: public base::android::SupportsBinder<PeerConnectorInterface> {
public:
using Proxy = PeerConnectorInterface::BinderRef;
// Transaction code for ConnectToPeer().
static constexpr transaction_code_t kConnectToPeer = 1;
// `callback` is the local endpoint's callback, which will be invoked with the
// peer's binder as soon as it's received; or with a null binder if we're
// destroyed first.
explicit PeerConnector(ExchangeBindersCallback callback)
: callback_(std::move(callback)) {}
// Sends an endpoint binder to a remote PeerConnector instance via `proxy`.
// The message is received in OnBinderTransaction() below.
static base::android::BinderStatusOr<void> ConnectToPeer(
Proxy& proxy,
base::android::BinderRef peer_endpoint_binder) {
ASSIGN_OR_RETURN(auto parcel, proxy.PrepareTransaction());
RETURN_IF_ERROR(
parcel.writer().WriteBinder(std::move(peer_endpoint_binder)));
return proxy.TransactOneWay(kConnectToPeer, std::move(parcel));
}
// Drops this connector's callback without invocation.
void Cancel() { std::ignore = TakeCallback(); }
private:
~PeerConnector() override {
if (callback_) {
// If our binder loses all references and we're destroyed without invoking
// the callback, invoke with a null binder to convey failure. The callback
// is required to be safe to call from any thread.
std::move(callback_).Run(base::android::BinderRef());
}
}
ExchangeBindersCallback TakeCallback() {
base::AutoLock lock(lock_);
// Note that moved-from callbacks are guaranteed to be null after the move.
return std::move(callback_);
}
// base::android::SupportsBinder<PeerConnectorInterface>:
base::android::BinderStatusOr<void> OnBinderTransaction(
transaction_code_t code,
const base::android::ParcelReader& in,
const base::android::ParcelWriter& out) override {
if (code != kConnectToPeer) {
return base::unexpected(STATUS_UNKNOWN_TRANSACTION);
}
ASSIGN_OR_RETURN(auto peer_endpoint_binder, in.ReadBinder());
ExchangeBindersCallback callback = TakeCallback();
if (!callback) {
// If the callback is null then we've already received a peer binder and
// invoked the callback with it.
return base::unexpected(STATUS_ALREADY_EXISTS);
}
std::move(callback).Run(std::move(peer_endpoint_binder));
return base::ok();
}
base::Lock lock_;
ExchangeBindersCallback callback_ GUARDED_BY(lock_);
};
// A broker which mediates binder exchange between two endpoints.
class Exchange : public base::RefCountedThreadSafe<Exchange> {
public:
Exchange() = default;
// Called by each of two ExchangeReceivers when they receive an endpoint
// binder. Well behaved clients will only call this once, but we still need to
// deal with multiple calls safely. `which` is always 0 or 1 and does not come
// from the client. See CreateBinderExchange() below.
base::android::BinderStatusOr<void> AcceptEndpoint(
int which,
base::android::BinderRef binder,
PeerConnector::Proxy connector) {
ReadyEndpoint ready_endpoint = {std::move(binder), std::move(connector)};
std::optional<ReadyEndpoint> first, second;
{
base::AutoLock lock(lock_);
auto& ours = endpoints_[which];
if (!absl::holds_alternative<NoEndpoint>(ours)) {
// There's no situation where we expect to receive a binder for this
// endpoint once its state has already changed in some way. Consider
// this to be a validation failure.
ours = DeadEndpoint{};
return base::unexpected(STATUS_ALREADY_EXISTS);
}
auto& theirs = endpoints_[1 - which];
if (absl::holds_alternative<DeadEndpoint>(theirs)) {
// The peer is already gone. We can silently drop this endpoint too.
ours = DeadEndpoint{};
return base::ok();
}
ours = std::move(ready_endpoint);
if (absl::holds_alternative<NoEndpoint>(theirs)) {
// Still waiting for the peer endpoint. The next time AcceptEndpoint()
// is called it should be for the peer's endpoint, and at that point
// they will discover that ours is ready too and proceed below.
return base::ok();
}
// If we're here, both endpoints are now ready to be exchanged.
first =
absl::get<ReadyEndpoint>(std::exchange(ours, ExchangedEndpoint()));
second =
absl::get<ReadyEndpoint>(std::exchange(theirs, ExchangedEndpoint()));
}
RETURN_IF_ERROR(PeerConnector::ConnectToPeer(first->connector,
std::move(second->binder)));
RETURN_IF_ERROR(PeerConnector::ConnectToPeer(second->connector,
std::move(first->binder)));
return base::ok();
}
void NotifyEndpointDisconnected(int which) {
// Disconnection is only interesting if it happens before we've received a
// binder for this endpoint.
base::AutoLock lock(lock_);
auto& ours = endpoints_[which];
if (absl::holds_alternative<NoEndpoint>(ours)) {
ours = DeadEndpoint{};
// If the peer endpoint was ready, disconnect it too. Otherwise the peer's
// state doesn't need to change: either it's already disconnected too or
// it hasn't been received yet and will be dropped once it arrives.
auto& theirs = endpoints_[1 - which];
if (absl::holds_alternative<ReadyEndpoint>(theirs)) {
theirs = DeadEndpoint{};
}
}
}
private:
friend class base::RefCountedThreadSafe<Exchange>;
// An endpoint which hasn't arrived yet.
struct NoEndpoint {};
// An endpoint which has arrived and is waiting for its peer.
struct ReadyEndpoint {
base::android::BinderRef binder;
PeerConnector::Proxy connector;
};
// An endpoint which was disconnected without providing a binder to itself.
struct DeadEndpoint {};
// An endpoint which arrived and has already been exchanged with its peer.
struct ExchangedEndpoint {};
using Endpoint =
absl::variant<NoEndpoint, ReadyEndpoint, DeadEndpoint, ExchangedEndpoint>;
~Exchange() = default;
base::Lock lock_;
// Tracks the state of both endpoints in this exchange.
std::array<Endpoint, 2> endpoints_ GUARDED_BY(lock_) = {NoEndpoint{},
NoEndpoint{}};
};
// Expects a single transaction containing two binders: one to an arbitrary
// endpoint object and one to a PeerConnector. These are forwarded to a local
// Exchange instance shared by exactly one other local ExchangeReceiver.
class ExchangeReceiver
: public base::android::SupportsBinder<ExchangeInterface> {
public:
using Proxy = ExchangeInterface::BinderRef;
// Transaction code for ExchangeBinders().
static constexpr transaction_code_t kExchangeBinders = 1;
// `which` is 0 or 1, identifying one of two endpoints for the Exchange. This
// receiver exclusively controls that endpoint on the Exchange.
ExchangeReceiver(scoped_refptr<Exchange> exchange, int which)
: exchange_(std::move(exchange)), which_(which) {}
// Sends an endpoint binder and a PeerConnector binder to a remote
// ExchangeReceiver instance via `proxy`. This message is received in
// OnBinderTransaction() below.
static base::android::BinderStatusOr<void> ExchangeBinders(
Proxy& proxy,
base::android::BinderRef endpoint_binder,
base::android::BinderRef connector_binder) {
ASSIGN_OR_RETURN(auto parcel, proxy.PrepareTransaction());
RETURN_IF_ERROR(parcel.writer().WriteBinder(std::move(endpoint_binder)));
RETURN_IF_ERROR(parcel.writer().WriteBinder(std::move(connector_binder)));
return proxy.TransactOneWay(kExchangeBinders, std::move(parcel));
}
private:
~ExchangeReceiver() override = default;
// base::android::SupportsBinder<ExchangeInterface>:
base::android::BinderStatusOr<void> OnBinderTransaction(
transaction_code_t code,
const base::android::ParcelReader& in,
const base::android::ParcelWriter& out) override {
if (code != kExchangeBinders) {
return base::unexpected(STATUS_UNKNOWN_TRANSACTION);
}
ASSIGN_OR_RETURN(auto endpoint_binder, in.ReadBinder());
ASSIGN_OR_RETURN(auto connector_binder, in.ReadBinder());
auto connector = PeerConnector::Proxy::Adopt(std::move(connector_binder));
if (!connector) {
return base::unexpected(STATUS_BAD_TYPE);
}
return exchange_->AcceptEndpoint(which_, std::move(endpoint_binder),
std::move(connector));
}
void OnBinderDestroyed() override {
exchange_->NotifyEndpointDisconnected(which_);
}
const scoped_refptr<Exchange> exchange_;
const int which_;
};
} // namespace
BinderPair CreateBinderExchange() {
auto exchange = base::MakeRefCounted<Exchange>();
auto receiver0 = base::MakeRefCounted<ExchangeReceiver>(exchange, 0);
auto receiver1 =
base::MakeRefCounted<ExchangeReceiver>(std::move(exchange), 1);
return {receiver0->GetBinder(), receiver1->GetBinder()};
}
base::android::BinderStatusOr<void> ExchangeBinders(
base::android::BinderRef exchange_binder,
base::android::BinderRef endpoint_binder,
ExchangeBindersCallback callback) {
CHECK(callback);
auto exchange = ExchangeReceiver::Proxy::Adopt(std::move(exchange_binder));
if (!exchange || !endpoint_binder) {
return base::unexpected(STATUS_BAD_TYPE);
}
auto connector = base::MakeRefCounted<PeerConnector>(std::move(callback));
auto result = ExchangeReceiver::ExchangeBinders(
exchange, std::move(endpoint_binder), connector->GetBinder());
if (!result.has_value()) {
// We're returning an error, so we must not allow the callback to be called.
connector->Cancel();
}
return result;
}
} // namespace mojo