/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef mozilla_ipc_NodeController_h #define mozilla_ipc_NodeController_h #include "mojo/core/ports/event.h" #include "mojo/core/ports/name.h" #include "mojo/core/ports/node.h" #include "mojo/core/ports/node_delegate.h" #include "chrome/common/ipc_message.h" #include "mozilla/ipc/ProtocolUtils.h" #include "nsTHashMap.h" #include "mozilla/Queue.h" #include "mozilla/DataMutex.h" #include "mozilla/UniquePtr.h" #include "mozilla/ipc/NodeChannel.h" namespace mozilla::ipc { class NodeController final : public mojo::core::ports::NodeDelegate, public NodeChannel::Listener { using NodeName = mojo::core::ports::NodeName; using PortName = mojo::core::ports::PortName; using PortRef = mojo::core::ports::PortRef; using Event = mojo::core::ports::Event; using Node = mojo::core::ports::Node; using UserData = mojo::core::ports::UserData; using PortStatus = mojo::core::ports::PortStatus; using UserMessageEvent = mojo::core::ports::UserMessageEvent; using UserMessage = mojo::core::ports::UserMessage; public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NodeController, override) // Return the global singleton instance. The returned value is only valid // while the IO thread is alive. static NodeController* GetSingleton(); class PortObserver : public UserData { public: virtual void OnPortStatusChanged() = 0; protected: ~PortObserver() override = default; }; // NOTE: For now there will always be a single broker process, and all // processes in the graph need to be able to talk to it (the parent process). // Give it a fixed node name for now to simplify things. // // If we ever decide to have multiple node networks intercommunicating (e.g. // multiple instances or background services), we may need to change this. static constexpr NodeName kBrokerNodeName{0x1, 0x1}; bool IsBroker() const { return mName == kBrokerNodeName; } // Mint a new connected pair of ports within the current process. std::pair CreatePortPair(); // Get a reference to the port with the given name. Returns an invalid // `PortRef` if the name wasn't found. PortRef GetPort(const PortName& aName); // Set the observer for the given port. This observer will be notified when // the status of the port changes. void SetPortObserver(const PortRef& aPort, PortObserver* aObserver); // See `mojo::core::ports::Node::GetStatus` Maybe GetStatus(const PortRef& aPort); // See `mojo::core::ports::Node::ClosePort` void ClosePort(const PortRef& aPort); // Send a message to the the port's connected peer. bool SendUserMessage(const PortRef& aPort, UniquePtr aMessage); // Get the next message from the port's message queue. // Will set `*aMessage` to the found message, or `nullptr`. // Returns `false` and sets `*aMessage` to `nullptr` if no further messages // will be delivered to this port as its peer has been closed. bool GetMessage(const PortRef& aPort, UniquePtr* aMessage); // Called in the broker process from GeckoChildProcessHost to introduce a new // child process into the network. Returns a `PortRef` which can be used to // communicate with the `PortRef` returned from `InitChildProcess`, and a // reference to the `NodeChannel` created for the new process. The port can // immediately have messages sent to it. std::tuple> InviteChildProcess( UniquePtr aChannel); // Called as the IO thread is started in the parent process. static void InitBrokerProcess(); // Called as the IO thread is started in a child process. static ScopedPort InitChildProcess(UniquePtr aChannel, int32_t aParentPid = -1); // Called when the IO thread is torn down. static void CleanUp(); private: explicit NodeController(const NodeName& aName); ~NodeController(); UniquePtr SerializeEventMessage( UniquePtr aEvent, const NodeName* aRelayTarget = nullptr, uint32_t aType = EVENT_MESSAGE_TYPE); UniquePtr DeserializeEventMessage(UniquePtr aMessage, NodeName* aRelayTarget = nullptr); // Get the `NodeChannel` for the named node. already_AddRefed GetNodeChannel(const NodeName& aName); // Stop communicating with this peer. Must be called on the IO thread. void DropPeer(NodeName aNodeName); // Message Handlers void OnEventMessage(const NodeName& aFromNode, UniquePtr aMessage) override; void OnBroadcast(const NodeName& aFromNode, UniquePtr aMessage) override; void OnIntroduce(const NodeName& aFromNode, NodeChannel::Introduction aIntroduction) override; void OnRequestIntroduction(const NodeName& aFromNode, const NodeName& aName) override; void OnAcceptInvite(const NodeName& aFromNode, const NodeName& aRealName, const PortName& aInitialPort) override; void OnChannelError(const NodeName& aFromNode) override; // NodeDelegate Implementation void ForwardEvent(const NodeName& aNode, UniquePtr aEvent) override; void BroadcastEvent(UniquePtr aEvent) override; void PortStatusChanged(const PortRef& aPortRef) override; const NodeName mName; const UniquePtr mNode; template using NodeMap = nsTHashMap; struct Invite { // The channel which is being invited. This will have a temporary name until // the invite is completed. RefPtr mChannel; // The port which will be merged with the port information from the new // child process when recieved. PortRef mToMerge; }; struct State { // Channels for connecting to all known peers. NodeMap> mPeers; // Messages which are queued for peers which we been introduced to yet. NodeMap, 64>> mPendingMessages; // Connections for peers being invited to the network. NodeMap mInvites; // Ports which are waiting to be merged by a particular peer node. NodeMap> mPendingMerges; }; DataMutex mState{"NodeController::mState"}; }; } // namespace mozilla::ipc #endif