// Copyright 2016 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_EDK_SYSTEM_NODE_CONTROLLER_H_
#define MOJO_EDK_SYSTEM_NODE_CONTROLLER_H_

#include <memory>
#include <queue>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

#include "base/callback.h"
#include "base/containers/hash_tables.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/task_runner.h"
#include "mojo/edk/embedder/platform_handle_vector.h"
#include "mojo/edk/embedder/platform_shared_buffer.h"
#include "mojo/edk/embedder/scoped_platform_handle.h"
#include "mojo/edk/system/atomic_flag.h"
#include "mojo/edk/system/node_channel.h"
#include "mojo/edk/system/ports/name.h"
#include "mojo/edk/system/ports/node.h"
#include "mojo/edk/system/ports/node_delegate.h"

namespace base {
class PortProvider;
}

namespace mojo {
namespace edk {

class Broker;
class Core;
class MachPortRelay;
class PortsMessage;

// The owner of ports::Node which facilitates core EDK implementation. All
// public interface methods are safe to call from any thread.
class NodeController : public ports::NodeDelegate,
                       public NodeChannel::Delegate {
 public:
  class PortObserver : public ports::UserData {
   public:
    virtual void OnPortStatusChanged() = 0;

   protected:
    ~PortObserver() override {}
  };

  // |core| owns and out-lives us.
  explicit NodeController(Core* core);
  ~NodeController() override;

  const ports::NodeName& name() const { return name_; }
  Core* core() const { return core_; }
  ports::Node* node() const { return node_.get(); }
  scoped_refptr<base::TaskRunner> io_task_runner() const {
    return io_task_runner_;
  }

#if defined(OS_MACOSX) && !defined(OS_IOS)
  // Create the relay used to transfer mach ports between processes.
  void CreateMachPortRelay(base::PortProvider* port_provider);
#endif

  // Called exactly once, shortly after construction, and before any other
  // methods are called on this object.
  void SetIOTaskRunner(scoped_refptr<base::TaskRunner> io_task_runner);

  // Connects this node to a child node. This node will initiate a handshake.
  void ConnectToChild(base::ProcessHandle process_handle,
                      ScopedPlatformHandle platform_handle,
                      const std::string& child_token,
                      const ProcessErrorCallback& process_error_callback);

  // Closes all reserved ports which associated with the child process
  // |child_token|.
  void CloseChildPorts(const std::string& child_token);

  // Connects this node to a parent node. The parent node will initiate a
  // handshake.
  void ConnectToParent(ScopedPlatformHandle platform_handle);

  // Sets a port's observer. If |observer| is null the port's current observer
  // is removed.
  void SetPortObserver(const ports::PortRef& port,
                       const scoped_refptr<PortObserver>& observer);

  // Closes a port. Use this in lieu of calling Node::ClosePort() directly, as
  // it ensures the port's observer has also been removed.
  void ClosePort(const ports::PortRef& port);

  // Sends a message on a port to its peer.
  int SendMessage(const ports::PortRef& port_ref,
                  std::unique_ptr<PortsMessage> message);

  // Reserves a local port |port| associated with |token|. A peer holding a copy
  // of |token| can merge one of its own ports into this one.
  void ReservePort(const std::string& token, const ports::PortRef& port,
                   const std::string& child_token);

  // Merges a local port |port| into a port reserved by |token| in the parent.
  void MergePortIntoParent(const std::string& token,
                           const ports::PortRef& port);

  // Merges two local ports together.
  int MergeLocalPorts(const ports::PortRef& port0, const ports::PortRef& port1);

  // Creates a new shared buffer for use in the current process.
  scoped_refptr<PlatformSharedBuffer> CreateSharedBuffer(size_t num_bytes);

  // Request that the Node be shut down cleanly. This may take an arbitrarily
  // long time to complete, at which point |callback| will be called.
  //
  // Note that while it is safe to continue using the NodeController's public
  // interface after requesting shutdown, you do so at your own risk and there
  // is NO guarantee that new messages will be sent or ports will complete
  // transfer.
  void RequestShutdown(const base::Closure& callback);

  // Notifies the NodeController that we received a bad message from the given
  // node.
  void NotifyBadMessageFrom(const ports::NodeName& source_node,
                            const std::string& error);

 private:
  friend Core;

  using NodeMap = std::unordered_map<ports::NodeName,
                                     scoped_refptr<NodeChannel>>;
  using OutgoingMessageQueue = std::queue<Channel::MessagePtr>;

  struct ReservedPort {
    ports::PortRef port;
    const std::string child_token;
  };

  void ConnectToChildOnIOThread(
      base::ProcessHandle process_handle,
      ScopedPlatformHandle platform_handle,
      ports::NodeName token,
      const ProcessErrorCallback& process_error_callback);
  void ConnectToParentOnIOThread(ScopedPlatformHandle platform_handle);

  scoped_refptr<NodeChannel> GetPeerChannel(const ports::NodeName& name);
  scoped_refptr<NodeChannel> GetParentChannel();
  scoped_refptr<NodeChannel> GetBrokerChannel();

  void AddPeer(const ports::NodeName& name,
               scoped_refptr<NodeChannel> channel,
               bool start_channel);
  void DropPeer(const ports::NodeName& name, NodeChannel* channel);
  void SendPeerMessage(const ports::NodeName& name,
                       ports::ScopedMessage message);
  void AcceptIncomingMessages();
  void ProcessIncomingMessages();
  void DropAllPeers();

  // ports::NodeDelegate:
  void GenerateRandomPortName(ports::PortName* port_name) override;
  void AllocMessage(size_t num_header_bytes,
                    ports::ScopedMessage* message) override;
  void ForwardMessage(const ports::NodeName& node,
                      ports::ScopedMessage message) override;
  void BroadcastMessage(ports::ScopedMessage message) override;
  void PortStatusChanged(const ports::PortRef& port) override;

  // NodeChannel::Delegate:
  void OnAcceptChild(const ports::NodeName& from_node,
                     const ports::NodeName& parent_name,
                     const ports::NodeName& token) override;
  void OnAcceptParent(const ports::NodeName& from_node,
                      const ports::NodeName& token,
                      const ports::NodeName& child_name) override;
  void OnAddBrokerClient(const ports::NodeName& from_node,
                         const ports::NodeName& client_name,
                         base::ProcessHandle process_handle) override;
  void OnBrokerClientAdded(const ports::NodeName& from_node,
                           const ports::NodeName& client_name,
                           ScopedPlatformHandle broker_channel) override;
  void OnAcceptBrokerClient(const ports::NodeName& from_node,
                            const ports::NodeName& broker_name,
                            ScopedPlatformHandle broker_channel) override;
  void OnPortsMessage(const ports::NodeName& from_node,
                      Channel::MessagePtr message) override;
  void OnRequestPortMerge(const ports::NodeName& from_node,
                          const ports::PortName& connector_port_name,
                          const std::string& token) override;
  void OnRequestIntroduction(const ports::NodeName& from_node,
                             const ports::NodeName& name) override;
  void OnIntroduce(const ports::NodeName& from_node,
                   const ports::NodeName& name,
                   ScopedPlatformHandle channel_handle) override;
  void OnBroadcast(const ports::NodeName& from_node,
                   Channel::MessagePtr message) override;
#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
  void OnRelayPortsMessage(const ports::NodeName& from_node,
                           base::ProcessHandle from_process,
                           const ports::NodeName& destination,
                           Channel::MessagePtr message) override;
  void OnPortsMessageFromRelay(const ports::NodeName& from_node,
                               const ports::NodeName& source_node,
                               Channel::MessagePtr message) override;
#endif
  void OnChannelError(const ports::NodeName& from_node,
                      NodeChannel* channel) override;
#if defined(OS_MACOSX) && !defined(OS_IOS)
  MachPortRelay* GetMachPortRelay() override;
#endif

  // Marks this NodeController for destruction when the IO thread shuts down.
  // This is used in case Core is torn down before the IO thread. Must only be
  // called on the IO thread.
  void DestroyOnIOThreadShutdown();

  // If there is a registered shutdown callback (meaning shutdown has been
  // requested, this checks the Node's status to see if clean shutdown is
  // possible. If so, shutdown is performed and the shutdown callback is run.
  void AttemptShutdownIfRequested();

  // These are safe to access from any thread as long as the Node is alive.
  Core* const core_;
  const ports::NodeName name_;
  const std::unique_ptr<ports::Node> node_;
  scoped_refptr<base::TaskRunner> io_task_runner_;

  // Guards |peers_| and |pending_peer_messages_|.
  base::Lock peers_lock_;

  // Channels to known peers, including parent and children, if any.
  NodeMap peers_;

  // Outgoing message queues for peers we've heard of but can't yet talk to.
  std::unordered_map<ports::NodeName, OutgoingMessageQueue>
      pending_peer_messages_;

  // Guards |reserved_ports_| and |pending_child_tokens_|.
  base::Lock reserved_ports_lock_;

  // Ports reserved by token. Key is the port token.
  base::hash_map<std::string, ReservedPort> reserved_ports_;
  // TODO(amistry): This _really_ needs to be a bimap. Unfortunately, we don't
  // have one yet :(
  std::unordered_map<ports::NodeName, std::string> pending_child_tokens_;

  // Guards |pending_port_merges_| and |reject_pending_merges_|.
  base::Lock pending_port_merges_lock_;

  // A set of port merge requests awaiting parent connection.
  std::vector<std::pair<std::string, ports::PortRef>> pending_port_merges_;

  // Indicates that new merge requests should be rejected because the parent has
  // disconnected.
  bool reject_pending_merges_ = false;

  // Guards |parent_name_| and |bootstrap_parent_channel_|.
  base::Lock parent_lock_;

  // The name of our parent node, if any.
  ports::NodeName parent_name_;

  // A temporary reference to the parent channel before we know their name.
  scoped_refptr<NodeChannel> bootstrap_parent_channel_;

  // Guards |broker_name_|, |pending_broker_clients_|, and
  // |pending_relay_messages_|.
  base::Lock broker_lock_;

  // The name of our broker node, if any.
  ports::NodeName broker_name_;

  // A queue of pending child names waiting to be connected to a broker.
  std::queue<ports::NodeName> pending_broker_clients_;

  // Messages waiting to be relayed by the broker once it's known.
  std::unordered_map<ports::NodeName, OutgoingMessageQueue>
      pending_relay_messages_;

  // Guards |incoming_messages_| and |incoming_messages_task_posted_|.
  base::Lock messages_lock_;
  std::queue<ports::ScopedMessage> incoming_messages_;
  // Ensures that there is only one incoming messages task posted to the IO
  // thread.
  bool incoming_messages_task_posted_ = false;

  // Guards |shutdown_callback_|.
  base::Lock shutdown_lock_;

  // Set by RequestShutdown(). If this is non-null, the controller will
  // begin polling the Node to see if clean shutdown is possible any time the
  // Node's state is modified by the controller.
  base::Closure shutdown_callback_;
  // Flag to fast-path checking |shutdown_callback_|.
  AtomicFlag shutdown_callback_flag_;

  // All other fields below must only be accessed on the I/O thread, i.e., the
  // thread on which core_->io_task_runner() runs tasks.

  // Channels to children during handshake.
  NodeMap pending_children_;

  // Indicates whether this object should delete itself on IO thread shutdown.
  // Must only be accessed from the IO thread.
  bool destroy_on_io_thread_shutdown_ = false;

#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_NACL_SFI)
  // Broker for sync shared buffer creation (non-Mac posix-only) in children.
  std::unique_ptr<Broker> broker_;
#endif

#if defined(OS_MACOSX) && !defined(OS_IOS)
  base::Lock mach_port_relay_lock_;
  // Relay for transferring mach ports to/from children.
  std::unique_ptr<MachPortRelay> mach_port_relay_;
#endif

  DISALLOW_COPY_AND_ASSIGN(NodeController);
};

}  // namespace edk
}  // namespace mojo

#endif  // MOJO_EDK_SYSTEM_NODE_CONTROLLER_H_