// Copyright 2019 The Chromium Authors. All rights reserved.
// 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_REMOTE_H_
#define MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_REMOTE_H_

#include <cstdint>
#include <utility>

#include "base/callback_forward.h"
#include "base/check.h"
#include "base/compiler_specific.h"
#include "base/macros.h"
#include "base/memory/scoped_refptr.h"
#include "base/task/sequenced_task_runner_forward.h"
#include "mojo/public/cpp/bindings/associated_interface_ptr_info.h"
#include "mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"

namespace mojo {

// An AssociatedRemote is similar to a Remote (see remote.h): it is used to
// issue mojom interface method calls that will be sent over a message pipe to
// be handled by the entangled AssociatedReceiver.
//
// An AssociatedRemote 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 AssociatedRemote cannot be used to issue 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>
class AssociatedRemote {
 public:
  using InterfaceType = Interface;
  using PendingType = PendingAssociatedRemote<Interface>;
  using Proxy = typename Interface::Proxy_;

  // Constructs an unbound AssociatedRemote. This object cannot issue Interface
  // method calls and does not schedule any tasks.
  AssociatedRemote() = default;
  AssociatedRemote(AssociatedRemote&& other) noexcept {
    *this = std::move(other);
  }

  // Constructs a new AssociatedRemote which is bound from |pending_remote| and
  // which schedules response callbacks and disconnection notifications on the
  // default SequencedTaskRunner (i.e., base::SequencedTaskRunnerHandle::Get()
  // at construction time).
  explicit AssociatedRemote(PendingAssociatedRemote<Interface> pending_remote)
      : AssociatedRemote(std::move(pending_remote), nullptr) {}

  // Constructs a new AssociatedRemote which is bound from |pending_remote| and
  // which schedules response callbacks and disconnection notifications on
  // |task_runner|. |task_runner| must run tasks on the same sequence that owns
  // this AssociatedRemote.
  AssociatedRemote(PendingAssociatedRemote<Interface> pending_remote,
                   scoped_refptr<base::SequencedTaskRunner> task_runner) {
    Bind(std::move(pending_remote), std::move(task_runner));
  }

  AssociatedRemote(const AssociatedRemote&) = delete;
  AssociatedRemote& operator=(const AssociatedRemote&) = delete;

  ~AssociatedRemote() = default;

  AssociatedRemote& operator=(AssociatedRemote&& other) noexcept {
    internal_state_.Swap(&other.internal_state_);
    return *this;
  }

  // Exposes access to callable Interface methods directed at this
  // AssociatedRemote's receiver. Must only be called on a bound
  // AssociatedRemote.
  typename Interface::Proxy_* get() const {
    DCHECK(is_bound())
        << "Cannot issue Interface method calls on an unbound AssociatedRemote";
    return internal_state_.instance();
  }

  // Shorthand form of |get()|. See above.
  typename Interface::Proxy_* operator->() const { return get(); }
  typename Interface::Proxy_& operator*() const { return *get(); }

  // Indicates whether this AssociatedRemote is bound and thus can issue
  // Interface method calls via the above accessors.
  //
  // NOTE: The state of being "bound" should not be confused with the state of
  // being "connected" (see |is_connected()| below). An AssociatedRemote is
  // NEVER passively unbound and the only way for it to become unbound is to
  // explicitly call |reset()| or |Unbind()|. As such, unless you make explicit
  // calls to those methods, it is always safe to assume that an
  // AssociatedRemote you've bound will remain bound and callable.
  bool is_bound() const { return internal_state_.is_bound(); }
  explicit operator bool() const { return is_bound(); }

  // Indicates whether this AssociatedRemote is connected to a receiver. Must
  // only be called on a bound AssociatedRemote. If this returns |true|, method
  // calls made by this AssociatedRemote may eventually end up at the connected
  // receiver (though it's of course possible for this call to race with
  // disconnection). If this returns |false| however, all future Interface
  // method calls on this AssociatedRemote will be silently dropped.
  //
  // A bound AssociatedRemote becomes disconnected automatically either when its
  // receiver is destroyed, or when it receives a malformed or otherwise
  // unexpected response message from the receiver.
  //
  // NOTE: The state of being "bound" should not be confused with the state of
  // being "connected". See |is_bound()| above.
  bool is_connected() const {
    DCHECK(is_bound());
    return !internal_state_.encountered_error();
  }

  // Sets a Closure to be invoked if this AssociatedRemote is cut off from its
  // receiver. This can happen if the corresponding AssociatedReceiver (or
  // unconsumed PendingAssociatedReceiver) is destroyed, or if the
  // AssociatedReceiver sends a malformed or otherwise unexpected response
  // message to this AssociatedRemote. Must only be called on a bound
  // AssociatedRemote object, and only remains set as long as the
  // AssociatedRemote is both bound and connected.
  //
  // If invoked at all, |handler| will be scheduled asynchronously using the
  // AssociatedRemote's bound SequencedTaskRunner.
  void set_disconnect_handler(base::OnceClosure handler) {
    if (is_connected())
      internal_state_.set_connection_error_handler(std::move(handler));
  }

  // Similar to above but the handler receives additional metadata if provided
  // by the receiving endpoint when closing itself.
  void set_disconnect_with_reason_handler(
      ConnectionErrorWithReasonCallback handler) {
    internal_state_.set_connection_error_with_reason_handler(
        std::move(handler));
  }

  // A convenient helper that resets this AssociatedRemote on disconnect. Note
  // that this replaces any previously set disconnection handler. Must be called
  // on a bound AssociatedRemote object. If the AssociatedRemote is connected,
  // a callback is set to reset it after it is disconnected. If AssociatedRemote
  // is bound but disconnected then reset is called immediately.
  void reset_on_disconnect() {
    if (!is_connected()) {
      reset();
      return;
    }
    set_disconnect_handler(
        base::BindOnce(&AssociatedRemote::reset, base::Unretained(this)));
  }

  // Resets this AssociatedRemote to an unbound state. To reset the
  // AssociatedRemote and recover an PendingAssociatedRemote that can be bound
  // again later, use |Unbind()| instead.
  void reset() {
    State doomed_state;
    internal_state_.Swap(&doomed_state);
  }

  // Similar to the method above, but also specifies a disconnect reason.
  void ResetWithReason(uint32_t custom_reason, const std::string& description) {
    if (internal_state_.is_bound())
      internal_state_.CloseWithReason(custom_reason, description);
    reset();
  }

  // Helpers for binding and unbinding the AssociatedRemote. Only an unbound
  // AssociatedRemote (i.e. |is_bound()| is false) may be bound. Similarly, only
  // a bound AssociatedRemote may be unbound.

  // Binds this AssociatedRemote with the returned PendingAssociatedReceiver.
  // Mojom interface method calls made through |this| will be routed to the
  // object that ends up binding the returned PendingAssociatedReceiver.
  //
  // Any response callbacks or disconnection notifications will be scheduled to
  // run on |task_runner|. If |task_runner| is null, defaults to the current
  // SequencedTaskRunner.
  PendingAssociatedReceiver<Interface> BindNewEndpointAndPassReceiver(
      scoped_refptr<base::SequencedTaskRunner> task_runner = nullptr)
      WARN_UNUSED_RESULT {
    DCHECK(!is_bound()) << "AssociatedRemote is already bound";

    ScopedInterfaceEndpointHandle remote_handle;
    ScopedInterfaceEndpointHandle receiver_handle;
    ScopedInterfaceEndpointHandle::CreatePairPendingAssociation(
        &remote_handle, &receiver_handle);
    Bind(PendingAssociatedRemote<Interface>(std::move(remote_handle), 0),
         std::move(task_runner));
    return PendingAssociatedReceiver<Interface>(std::move(receiver_handle));
  }

  // Binds this AssociatedRemote by consuming |pending_remote|.
  //
  // Any response callbacks or disconnection notifications will be scheduled to
  // run on |task_runner|. If |task_runner| is null, defaults to the current
  // SequencedTaskRunner.
  void Bind(PendingAssociatedRemote<Interface> pending_remote,
            scoped_refptr<base::SequencedTaskRunner> task_runner = nullptr) {
    DCHECK(!is_bound()) << "AssociatedRemote is already bound";

    if (!pending_remote) {
      reset();
      return;
    }

    internal_state_.Bind(
        AssociatedInterfacePtrInfo<Interface>(pending_remote.PassHandle(),
                                              pending_remote.version()),
        std::move(task_runner));

    // Force the internal state to configure its proxy. Unlike InterfacePtr we
    // do not use AssociatedRemote in transit, so binding to a pipe handle can
    // also imply binding to a SequencedTaskRunner and observing pipe handle
    // state.
    ignore_result(internal_state_.instance());
  }

  // Binds this AssociatedRemote with the returned PendingAssociatedReceiver
  // 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.
  PendingAssociatedReceiver<Interface> BindNewEndpointAndPassDedicatedReceiver()
      WARN_UNUSED_RESULT {
    DCHECK(!is_bound()) << "AssociatedReceiver is already bound";

    PendingAssociatedReceiver<Interface> receiver =
        BindNewEndpointAndPassReceiver();
    receiver.EnableUnassociatedUsage();
    return receiver;
  }

  // Unbinds this AssociatedRemote, rendering it unable to issue further
  // Interface method calls. Returns a PendingAssociatedRemote which may be
  // passed across threads or processes and consumed by another AssociatedRemote
  // elsewhere.
  //
  // Note that it is an error (the bad, crashy kind of error) to attempt to
  // |Unbind()| an AssociatedRemote which is awaiting one or more responses to
  // previously issued Interface method calls. Calling this method should only
  // be considered in cases where satisfaction of that constraint can be proven.
  //
  // Must only be called on a bound AssociatedRemote.
  PendingAssociatedRemote<Interface> Unbind() WARN_UNUSED_RESULT {
    DCHECK(is_bound());
    CHECK(!internal_state_.has_pending_callbacks());
    State state;
    internal_state_.Swap(&state);
    AssociatedInterfacePtrInfo<Interface> info = state.PassInterface();
    return PendingAssociatedRemote<Interface>(info.PassHandle(),
                                              info.version());
  }

  // 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.
  void FlushForTesting() { internal_state_.FlushForTesting(); }

  internal::AssociatedInterfacePtrState<Interface>* internal_state() {
    return &internal_state_;
  }

 private:
  using State = internal::AssociatedInterfacePtrState<Interface>;
  mutable State internal_state_;
};

}  // namespace mojo

#endif  // MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_REMOTE_H_
