blob: 76065029e088c48b7c43433e562c13d286b7aafa [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_RECEIVER_H_
#define MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_RECEIVER_H_
#include <stdint.h>
#include <memory>
#include <string_view>
#include <utility>
#include "base/check.h"
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/memory/ptr_util.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/task/sequenced_task_runner.h"
#include "mojo/public/cpp/bindings/lib/sync_method_traits.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
#include "mojo/public/cpp/bindings/raw_ptr_impl_ref_traits.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/runtime_features.h"
namespace mojo {
class MessageFilter;
class MessageReceiver;
namespace internal {
// Base class containing common code for various AssociatedReceiver template
// expansions to reduce code size.
class COMPONENT_EXPORT(MOJO_CPP_BINDINGS) AssociatedReceiverBase {
public:
AssociatedReceiverBase();
void SetFilter(std::unique_ptr<MessageFilter> filter);
void reset();
void ResetWithReason(uint32_t custom_reason, std::string_view description);
void set_disconnect_handler(base::OnceClosure error_handler);
void set_disconnect_with_reason_handler(
ConnectionErrorWithReasonCallback error_handler);
void reset_on_disconnect();
bool is_bound() const { return !!endpoint_client_; }
explicit operator bool() const { return !!endpoint_client_; }
void FlushForTesting();
// Please see comments on the same method of InterfaceEndpointClient.
void ResetFromAnotherSequenceUnsafe() {
if (endpoint_client_)
endpoint_client_->ResetFromAnotherSequenceUnsafe();
}
protected:
~AssociatedReceiverBase();
// TODO(dcheng): should probably document this thing.
void BindImpl(ScopedInterfaceEndpointHandle handle,
MessageReceiverWithResponderStatus* receiver,
std::unique_ptr<MessageReceiver> payload_validator,
base::span<const uint32_t> sync_method_ordinals,
scoped_refptr<base::SequencedTaskRunner> runner,
uint32_t interface_version,
const char* interface_name,
MessageToMethodInfoCallback method_info_callback,
MessageToMethodNameCallback method_name_callback);
std::unique_ptr<InterfaceEndpointClient> endpoint_client_;
};
} // namespace internal
// An AssociatedReceiver is similar to a Receiver (see receiver.h): it receives
// incoming mojom interface method calls (sent over a message pipe from its
// entangled AssociatedRemote) and dispatches them to a concrete C++
// implementation.
//
// An AssociatedReceiver is needed when it is important to preserve the relative
// ordering of calls with another mojom interface. This is implemented by
// sharing the underlying message pipe between the mojom interfaces where
// ordering must be preserved.
//
// Because of this, an AssociatedReceiver will not receive any mojom interface
// method calls until one of its endpoints (either the AssociatedRemote itself
// or its entangled AssociatedReceiver) is sent over a Remote/Receiver pair
// or an already-established AssociatedRemote/AssociatedReceiver pair.
template <typename Interface,
typename ImplRefTraits = RawPtrImplRefTraits<Interface>>
class AssociatedReceiver : public internal::AssociatedReceiverBase {
public:
// Typically (and by default) an AssociatedReceiver uses a raw pointer to
// reference its linked Interface implementation object, because typically
// that implementation object owns the AssociatedReceiver. An alternative
// |ImplRefTraits| may be provided as a second AssociatedReceiver template
// argument in order to use a different reference type.
using ImplPointerType = typename ImplRefTraits::PointerType;
// Constructs an unbound AssociatedReceiver linked to |impl| for the duration
// of the AssociatedReceiver's lifetime. The AssociatedReceiver can be bound
// later by calling |Bind()| or |BindNewEndpointAndPassRemote()|. An unbound
// AssociatedReceiver does not schedule any asynchronous tasks.
explicit AssociatedReceiver(ImplPointerType impl) {
stub_.set_sink(std::move(impl));
}
// Constructs a bound AssociatedReceiver by consuming |pending_receiver|. The
// AssociatedReceiver is permanently linked to |impl| and will schedule
// incoming |impl| method and disconnection notifications on the default
// SequencedTaskRunner (i.e. base::SequencedTaskRunner::GetCurrentDefault() at
// construction time).
AssociatedReceiver(ImplPointerType impl,
PendingAssociatedReceiver<Interface> pending_receiver)
: AssociatedReceiver(std::move(impl),
std::move(pending_receiver),
nullptr) {}
// Similar to above but the constructed AssociatedReceiver schedules all tasks
// via |task_runner| instead of the default SequencedTaskRunner. |task_runner|
// must run tasks on the same sequence that owns this AssociatedReceiver.
AssociatedReceiver(ImplPointerType impl,
PendingAssociatedReceiver<Interface> pending_receiver,
scoped_refptr<base::SequencedTaskRunner> task_runner)
: AssociatedReceiver(std::move(impl)) {
Bind(std::move(pending_receiver), std::move(task_runner));
}
AssociatedReceiver(const AssociatedReceiver&) = delete;
AssociatedReceiver& operator=(const AssociatedReceiver&) = delete;
~AssociatedReceiver() = default;
// Indicates whether this AssociatedReceiver is bound, meaning it may continue
// to receive Interface method calls from a remote caller.
//
// NOTE: An AssociatedReceiver is NEVER passively unbound. The only way for it
// to become unbound is to explicitly call |reset()| or |Unbind()|.
using AssociatedReceiverBase::is_bound;
// Sets a OnceClosure to be invoked if this AssociatedReceiver is cut off from
// its AssociatedRemote (or PendingAssociatedRemote). This can happen if the
// corresponding AssociatedRemote (or unconsumed PendingAssociatedRemote) has
// been destroyed, or if the AssociatedRemote sends a malformed message. Must
// only be called on a bound AssociatedReceiver object, and only remains set
// as long as the AssociatedReceiver is both bound and connected.
//
// If ever invoked, |handler| will be scheduled asynchronously on the
// AssociatedReceiver's bound SequencedTaskRunner.
using AssociatedReceiverBase::set_disconnect_handler;
// Like above but when invoked |handler| will receive additional metadata
// about why the remote endpoint was closed, if provided.
using AssociatedReceiverBase::set_disconnect_with_reason_handler;
// Resets this AssociatedReceiver on disconnect. Note that this replaces any
// previously set disconnection handler. Must be called on a bound
// AssociatedReceiver object, and only remains set as long as the
// AssociatedReceiver is both bound and connected.
using AssociatedReceiverBase::reset_on_disconnect;
// Resets this AssociatedReceiver to an unbound state. An unbound
// AssociatedReceiver will NEVER schedule method calls or disconnection
// notifications, and any pending tasks which were scheduled prior to
// unbinding are effectively cancelled.
using AssociatedReceiverBase::reset;
// Similar to above but provides additional information to the remote endpoint
// about why this end is hanging up.
using AssociatedReceiverBase::ResetWithReason;
// Helpers for binding and unbinding the AssociatedReceiver. Only an unbound
// AssociatedReceiver (i.e. |is_bound()| is false) may be bound. Similarly,
// only a bound AssociatedReceiver may be unbound.
// Binds this AssociatedReceiver with the returned PendingAssociatedRemote.
// Mojom interface method calls made via the returned remote will be routed
// and dispatched to |impl()|.
//
// Any incoming method calls or disconnection notifications will be scheduled
// to run on |task_runner|. If |task_runner| is null, this defaults to the
// current SequencedTaskRunner.
[[nodiscard]] PendingAssociatedRemote<Interface> BindNewEndpointAndPassRemote(
scoped_refptr<base::SequencedTaskRunner> task_runner = nullptr) {
DCHECK(!is_bound()) << "AssociatedReceiver for " << Interface::Name_
<< " is already bound";
PendingAssociatedRemote<Interface> remote;
if (!internal::GetRuntimeFeature_ExpectEnabled<Interface>()) {
return remote;
}
Bind(remote.InitWithNewEndpointAndPassReceiver(), std::move(task_runner));
return remote;
}
// Binds this AssociatedReceiver by consuming |pending_receiver|.
//
// Any incoming method calls or disconnection notifications will be scheduled
// to run on |task_runner|. If |task_runner| is null, this defaults to the
// current SequencedTaskRunner.
void Bind(PendingAssociatedReceiver<Interface> pending_receiver,
scoped_refptr<base::SequencedTaskRunner> task_runner = nullptr) {
DCHECK(!is_bound()) << "AssociatedReceiver for " << Interface::Name_
<< " is already bound";
if (!pending_receiver) {
reset();
return;
}
if (!internal::GetRuntimeFeature_ExpectEnabled<Interface>()) {
reset();
return;
}
BindImpl(pending_receiver.PassHandle(), &stub_,
base::WrapUnique(new typename Interface::RequestValidator_()),
internal::SyncMethodTraits<Interface>::GetOrdinals(),
std::move(task_runner), Interface::Version_, Interface::Name_,
Interface::MessageToMethodInfo_, Interface::MessageToMethodName_);
}
// Binds this AssociatedReceiver with the returned PendingAssociatedRemote
// using a dedicated message pipe. This allows the entangled
// AssociatedReceiver/AssociatedRemote endpoints to be used without ever being
// associated with any other mojom interfaces.
//
// Needless to say, messages sent between the two entangled endpoints will not
// be ordered with respect to any other mojom interfaces. This is generally
// useful for ignoring calls on an associated remote or for binding associated
// endpoints in tests.
[[nodiscard]] PendingAssociatedRemote<Interface>
BindNewEndpointAndPassDedicatedRemote() {
DCHECK(!is_bound()) << "AssociatedReceiver for " << Interface::Name_
<< " is already bound";
PendingAssociatedRemote<Interface> remote = BindNewEndpointAndPassRemote();
if (remote) {
remote.EnableUnassociatedUsage();
}
return remote;
}
// Unbinds this AssociatedReceiver, preventing any further |impl| method calls
// or disconnection notifications from being scheduled by it. Any such tasks
// that were scheduled prior to unbinding are effectively cancelled.
//
// Returns a PendingAssociatedReceiver which remains connected to this
// receiver's AssociatedRemote and which may be transferred elsewhere and
// consumed by another AssociatedReceiver. Any messages received but not
// actually dispatched by this AssociatedReceiver remain intact within the
// returned PendingAssociatedReceiver and can be dispatched by whomever binds
// with it later.
//
//
// Note that an AssociatedReceiver should not be unbound while there are still
// living response callbacks that haven't been invoked, as once the
// AssociatedReceiver is unbound those response callbacks are no longer valid
// and the AssociatedRemote will never be able to receive its expected
// responses.
[[nodiscard]] PendingAssociatedReceiver<Interface> Unbind() {
DCHECK(is_bound());
// TODO(dcheng): Consider moving implementation into base class:
// std::exchange(endpoint_client_, nullptr)->PassHandle();
PendingAssociatedReceiver<Interface> pending_receiver(
endpoint_client_->PassHandle());
endpoint_client_.reset();
return pending_receiver;
}
// Sets a message filter to be notified of each incoming message before
// dispatch. If a filter returns |false| from Accept(), the message is not
// dispatched and the pipe is closed. Filters cannot be removed once added
// and only one can be set.
using AssociatedReceiverBase::SetFilter;
// Sends a message on the underlying message pipe and runs the current
// message loop until its response is received. This can be used in tests to
// verify that no message was sent on a message pipe in response to some
// stimulus.
using AssociatedReceiverBase::FlushForTesting;
// Returns the interface implementation that was previously specified.
Interface* impl() { return ImplRefTraits::GetRawPointer(&stub_.sink()); }
// Allows test code to swap the interface implementation.
//
// Returns the existing interface implementation to the caller.
//
// The caller needs to guarantee that `new_impl` will live longer than
// `this` AssociatedReceiver. One way to achieve this is to store
// the returned `old_impl` and swap it back in when `new_impl` is getting
// destroyed.
// Test code should prefer using `mojo::test::ScopedSwapImplForTesting` if
// possible.
[[nodiscard]] ImplPointerType SwapImplForTesting(ImplPointerType new_impl) {
return std::exchange(stub_.sink(), std::move(new_impl));
}
// Reports the currently dispatching message as bad and resets this receiver.
// Note that this is only legal to call from within the stack frame of a
// message dispatch. If you need to do asynchronous work before determining
// the legitimacy of a message, use GetBadMessageCallback() and retain its
// result until ready to invoke or discard it.
NOT_TAIL_CALLED void ReportBadMessage(const std::string& error) {
GetBadMessageCallback().Run(error);
}
// Acquires a callback which may be run to report the currently dispatching
// message as bad and reset this receiver. Note that this is only legal to
// call from directly within stack frame of a message dispatch, but the
// returned callback may be called exactly once any time thereafter to report
// the message as bad. |GetBadMessageCallback()| may only be called once per
// message, and the returned callback must be run on the same sequence to
// which this Receiver is bound.
ReportBadMessageCallback GetBadMessageCallback() {
return base::BindOnce(
[](ReportBadMessageCallback inner_callback,
base::WeakPtr<AssociatedReceiver> receiver, std::string_view error) {
std::move(inner_callback).Run(error);
if (receiver)
receiver->reset();
},
mojo::GetBadMessageCallback(), weak_ptr_factory_.GetWeakPtr());
}
typename Interface::template Stub_<ImplRefTraits> stub_;
base::WeakPtrFactory<AssociatedReceiver> weak_ptr_factory_{this};
};
// Associates |handle| with a dedicated and disconnected message pipe.
// Generally, |handle| should be the receiving side of an entangled
// AssociatedReceiver/AssociatedRemote pair, which allows the AssociatedRemote
// to be used to make calls that will be silently dropped.
COMPONENT_EXPORT(MOJO_CPP_BINDINGS)
void AssociateWithDisconnectedPipe(ScopedInterfaceEndpointHandle handle);
} // namespace mojo
#endif // MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_RECEIVER_H_