Adds associated interface support to IPC::Channel
Provides a way of registering associated interface bindings with
an IPC::Channel endpoint and acquiring proxies to remote interfaces
on the other side.
Support for this is exposed by IPC::Channel but only implemented
in IPC::ChannelMojo.
This is part a series of CLs to support Channel-associated interfaces.
BUG=612500
Committed: https://crrev.com/dc88e5075878f16cde23b910d2ce19aa862129e4
Review-Url: https://codereview.chromium.org/2137353002
Cr-Original-Commit-Position: refs/heads/master@{#405316}
Cr-Commit-Position: refs/heads/master@{#405364}
diff --git a/ipc/BUILD.gn b/ipc/BUILD.gn
index 34cab68..8dba71d 100644
--- a/ipc/BUILD.gn
+++ b/ipc/BUILD.gn
@@ -167,6 +167,13 @@
]
}
+mojom("test_interfaces") {
+ testonly = true
+ sources = [
+ "ipc_test.mojom",
+ ]
+}
+
# This is provided as a separate target so other targets can provide param
# traits implementations without necessarily linking to all of IPC.
source_set("param_traits") {
@@ -235,6 +242,7 @@
":ipc",
":mojom",
":run_all_unittests",
+ ":test_interfaces",
":test_support",
"//base",
"//base:i18n",
diff --git a/ipc/ipc.gyp b/ipc/ipc.gyp
index 3226899..56242d6 100644
--- a/ipc/ipc.gyp
+++ b/ipc/ipc.gyp
@@ -61,6 +61,14 @@
],
},
{
+ 'target_name': 'ipc_test_interfaces',
+ 'type': 'static_library',
+ 'sources': [
+ 'ipc_test.mojom',
+ ],
+ 'includes': [ '../mojo/mojom_bindings_generator.gypi' ],
+ },
+ {
'target_name': 'ipc_run_all_unittests',
'type': 'static_library',
'dependencies': [
@@ -83,6 +91,7 @@
'dependencies': [
'ipc',
'ipc_run_all_unittests',
+ 'ipc_test_interfaces',
'test_support_ipc',
'../base/base.gyp:base',
'../base/base.gyp:base_i18n',
diff --git a/ipc/ipc.mojom b/ipc/ipc.mojom
index 681da12..28d73fb 100644
--- a/ipc/ipc.mojom
+++ b/ipc/ipc.mojom
@@ -17,8 +17,14 @@
Type type;
};
+// A placeholder interface type since we don't yet support generic associated
+// message pipe handles.
+interface GenericInterface {};
+
interface Channel {
Receive(array<uint8> data, array<SerializedHandle>? handles);
+
+ GetAssociatedInterface(string name, associated GenericInterface& request);
};
// An interface for connecting a pair of Channel interfaces representing a
diff --git a/ipc/ipc_channel.h b/ipc/ipc_channel.h
index 09baf6c..1fc9c6c 100644
--- a/ipc/ipc_channel.h
+++ b/ipc/ipc_channel.h
@@ -18,6 +18,10 @@
#include "ipc/ipc_channel_handle.h"
#include "ipc/ipc_endpoint.h"
#include "ipc/ipc_message.h"
+#include "mojo/public/cpp/bindings/associated_group.h"
+#include "mojo/public/cpp/bindings/associated_interface_ptr.h"
+#include "mojo/public/cpp/bindings/associated_interface_request.h"
+#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
#if defined(OS_POSIX)
#include <sys/types.h>
@@ -93,6 +97,62 @@
CLOSE_FD_MESSAGE_TYPE = HELLO_MESSAGE_TYPE - 1
};
+ // Helper interface a Channel may implement to expose support for associated
+ // Mojo interfaces.
+ class IPC_EXPORT AssociatedInterfaceSupport {
+ public:
+ using GenericAssociatedInterfaceFactory =
+ base::Callback<void(mojo::ScopedInterfaceEndpointHandle)>;
+
+ virtual ~AssociatedInterfaceSupport() {}
+
+ // Accesses the AssociatedGroup used to associate new interface endpoints
+ // with this Channel.
+ virtual mojo::AssociatedGroup* GetAssociatedGroup() = 0;
+
+ // Adds an interface factory to this channel for interface |name|.
+ virtual void AddGenericAssociatedInterface(
+ const std::string& name,
+ const GenericAssociatedInterfaceFactory& factory) = 0;
+
+ // Requests an associated interface from the remote endpoint.
+ virtual void GetGenericRemoteAssociatedInterface(
+ const std::string& name,
+ mojo::ScopedInterfaceEndpointHandle handle) = 0;
+
+ // Template helper to add an interface factory to this channel.
+ template <typename Interface>
+ using AssociatedInterfaceFactory =
+ base::Callback<void(mojo::AssociatedInterfaceRequest<Interface>)>;
+ template <typename Interface>
+ void AddAssociatedInterface(
+ const AssociatedInterfaceFactory<Interface>& factory) {
+ AddGenericAssociatedInterface(
+ Interface::Name_,
+ base::Bind(&BindAssociatedInterfaceRequest<Interface>, factory));
+ }
+
+ // Template helper to request a remote associated interface.
+ template <typename Interface>
+ void GetRemoteAssociatedInterface(
+ mojo::AssociatedInterfacePtr<Interface>* proxy) {
+ mojo::AssociatedInterfaceRequest<Interface> request =
+ mojo::GetProxy(proxy, GetAssociatedGroup());
+ GetGenericRemoteAssociatedInterface(
+ Interface::Name_, request.PassHandle());
+ }
+
+ private:
+ template <typename Interface>
+ static void BindAssociatedInterfaceRequest(
+ const AssociatedInterfaceFactory<Interface>& factory,
+ mojo::ScopedInterfaceEndpointHandle handle) {
+ mojo::AssociatedInterfaceRequest<Interface> request;
+ request.Bind(std::move(handle));
+ factory.Run(std::move(request));
+ }
+ };
+
// The maximum message size in bytes. Attempting to receive a message of this
// size or bigger results in a channel error.
static const size_t kMaximumMessageSize = 128 * 1024 * 1024;
@@ -181,6 +241,11 @@
// Get its own process id. This value is told to the peer.
virtual base::ProcessId GetSelfPID() const = 0;
+ // Gets a helper for associating Mojo interfaces with this Channel.
+ //
+ // NOTE: Not all implementations support this.
+ virtual AssociatedInterfaceSupport* GetAssociatedInterfaceSupport();
+
// Overridden from ipc::Sender.
// Send a message over the Channel to the listener on the other end.
//
diff --git a/ipc/ipc_channel_common.cc b/ipc/ipc_channel_common.cc
index 3002b665..1d5d2f99 100644
--- a/ipc/ipc_channel_common.cc
+++ b/ipc/ipc_channel_common.cc
@@ -84,6 +84,10 @@
Channel::~Channel() {
}
+Channel::AssociatedInterfaceSupport* Channel::GetAssociatedInterfaceSupport() {
+ return nullptr;
+}
+
bool Channel::IsSendThreadSafe() const {
return false;
}
diff --git a/ipc/ipc_channel_mojo.cc b/ipc/ipc_channel_mojo.cc
index 22efa3a..70a12f4 100644
--- a/ipc/ipc_channel_mojo.cc
+++ b/ipc/ipc_channel_mojo.cc
@@ -307,6 +307,14 @@
listener_->OnChannelError();
}
+void ChannelMojo::OnAssociatedInterfaceRequest(
+ const std::string& name,
+ mojo::ScopedInterfaceEndpointHandle handle) {
+ auto iter = associated_interfaces_.find(name);
+ if (iter != associated_interfaces_.end())
+ iter->second.Run(std::move(handle));
+}
+
void ChannelMojo::InitMessageReader(mojom::ChannelAssociatedPtrInfo sender,
mojom::ChannelAssociatedRequest receiver,
base::ProcessId peer_pid) {
@@ -392,6 +400,9 @@
return bootstrap_->GetSelfPID();
}
+Channel::AssociatedInterfaceSupport*
+ChannelMojo::GetAssociatedInterfaceSupport() { return this; }
+
void ChannelMojo::OnMessageReceived(const Message& message) {
TRACE_EVENT2("ipc,toplevel", "ChannelMojo::OnMessageReceived",
"class", IPC_MESSAGE_ID_CLASS(message.type()),
@@ -468,4 +479,23 @@
return MOJO_RESULT_OK;
}
+mojo::AssociatedGroup* ChannelMojo::GetAssociatedGroup() {
+ DCHECK(bootstrap_);
+ return bootstrap_->GetAssociatedGroup();
+}
+
+void ChannelMojo::AddGenericAssociatedInterface(
+ const std::string& name,
+ const GenericAssociatedInterfaceFactory& factory) {
+ auto result = associated_interfaces_.insert({ name, factory });
+ DCHECK(result.second);
+}
+
+void ChannelMojo::GetGenericRemoteAssociatedInterface(
+ const std::string& name,
+ mojo::ScopedInterfaceEndpointHandle handle) {
+ DCHECK(message_reader_);
+ message_reader_->GetRemoteInterface(name, std::move(handle));
+}
+
} // namespace IPC
diff --git a/ipc/ipc_channel_mojo.h b/ipc/ipc_channel_mojo.h
index 774a82a..c267649 100644
--- a/ipc/ipc_channel_mojo.h
+++ b/ipc/ipc_channel_mojo.h
@@ -7,7 +7,9 @@
#include <stdint.h>
+#include <map>
#include <memory>
+#include <string>
#include <vector>
#include "base/macros.h"
@@ -37,6 +39,7 @@
//
class IPC_EXPORT ChannelMojo
: public Channel,
+ public Channel::AssociatedInterfaceSupport,
public MojoBootstrap::Delegate,
public NON_EXPORTED_BASE(internal::MessagePipeReader::Delegate) {
public:
@@ -62,6 +65,7 @@
bool IsSendThreadSafe() const override;
base::ProcessId GetPeerPID() const override;
base::ProcessId GetSelfPID() const override;
+ Channel::AssociatedInterfaceSupport* GetAssociatedInterfaceSupport() override;
#if defined(OS_POSIX) && !defined(OS_NACL_SFI)
int GetClientFileDescriptor() const override;
@@ -82,6 +86,9 @@
mojom::ChannelAssociatedRequest receive_channel,
int32_t peer_pid) override;
void OnBootstrapError() override;
+ void OnAssociatedInterfaceRequest(
+ const std::string& name,
+ mojo::ScopedInterfaceEndpointHandle handle) override;
// MessagePipeReader::Delegate
void OnMessageReceived(const Message& message) override;
@@ -96,6 +103,15 @@
mojom::ChannelAssociatedRequest receiver,
base::ProcessId peer_pid);
+ // Channel::AssociatedInterfaceSupport:
+ mojo::AssociatedGroup* GetAssociatedGroup() override;
+ void AddGenericAssociatedInterface(
+ const std::string& name,
+ const GenericAssociatedInterfaceFactory& factory) override;
+ void GetGenericRemoteAssociatedInterface(
+ const std::string& name,
+ mojo::ScopedInterfaceEndpointHandle handle) override;
+
// ChannelMojo needs to kill its MessagePipeReader in delayed manner
// because the channel wants to kill these readers during the
// notifications invoked by them.
@@ -108,6 +124,9 @@
std::unique_ptr<MojoBootstrap> bootstrap_;
Listener* listener_;
+ std::map<std::string, GenericAssociatedInterfaceFactory>
+ associated_interfaces_;
+
// Guards access to the fields below.
mutable base::Lock lock_;
std::unique_ptr<internal::MessagePipeReader, ReaderDeleter> message_reader_;
diff --git a/ipc/ipc_channel_mojo_unittest.cc b/ipc/ipc_channel_mojo_unittest.cc
index eec011b..e846c13 100644
--- a/ipc/ipc_channel_mojo_unittest.cc
+++ b/ipc/ipc_channel_mojo_unittest.cc
@@ -6,6 +6,7 @@
#include <stddef.h>
#include <stdint.h>
+
#include <memory>
#include <utility>
@@ -17,6 +18,7 @@
#include "base/pickle.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
+#include "base/strings/stringprintf.h"
#include "base/test/test_io_thread.h"
#include "base/test/test_timeouts.h"
#include "base/threading/thread.h"
@@ -26,6 +28,7 @@
#include "ipc/ipc_mojo_handle_attachment.h"
#include "ipc/ipc_mojo_message_helper.h"
#include "ipc/ipc_mojo_param_traits.h"
+#include "ipc/ipc_test.mojom.h"
#include "ipc/ipc_test_base.h"
#include "ipc/ipc_test_channel_listener.h"
#include "mojo/edk/test/mojo_test_base.h"
@@ -60,6 +63,12 @@
namespace {
+void SendString(IPC::Sender* sender, const std::string& str) {
+ IPC::Message* message = new IPC::Message(0, 2, IPC::Message::PRIORITY_NORMAL);
+ message->WriteString(str);
+ ASSERT_TRUE(sender->Send(message));
+}
+
class ListenerThatExpectsOK : public IPC::Listener {
public:
ListenerThatExpectsOK() : received_ok_(false) {}
@@ -83,12 +92,7 @@
DCHECK(received_ok_);
}
- static void SendOK(IPC::Sender* sender) {
- IPC::Message* message =
- new IPC::Message(0, 2, IPC::Message::PRIORITY_NORMAL);
- message->WriteString(std::string("OK"));
- ASSERT_TRUE(sender->Send(message));
- }
+ static void SendOK(IPC::Sender* sender) { SendString(sender, "OK"); }
private:
bool received_ok_;
@@ -124,7 +128,7 @@
class IPCChannelMojoTest : public testing::Test {
public:
- IPCChannelMojoTest() : io_thread_(base::TestIOThread::Mode::kAutoStart) {}
+ IPCChannelMojoTest() {}
void TearDown() override { base::RunLoop().RunUntilIdle(); }
@@ -148,7 +152,6 @@
private:
base::MessageLoop message_loop_;
- base::TestIOThread io_thread_;
mojo::edk::test::MultiprocessTestHelper helper_;
mojo::ScopedMessagePipeHandle handle_;
std::unique_ptr<IPC::Channel> channel_;
@@ -582,6 +585,120 @@
Close();
}
+class ListenerWithSimpleAssociatedInterface
+ : public IPC::Listener,
+ public IPC::mojom::SimpleTestDriver {
+ public:
+ static const int kNumMessages;
+
+ ListenerWithSimpleAssociatedInterface() : binding_(this) {}
+
+ ~ListenerWithSimpleAssociatedInterface() override {}
+
+ bool OnMessageReceived(const IPC::Message& message) override {
+ base::PickleIterator iter(message);
+ std::string should_be_expected;
+ EXPECT_TRUE(iter.ReadString(&should_be_expected));
+ EXPECT_EQ(should_be_expected, next_expected_string_);
+ num_messages_received_++;
+ return true;
+ }
+
+ void OnChannelError() override {
+ DCHECK(received_quit_);
+ }
+
+ void RegisterInterfaceFactory(IPC::Channel* channel) {
+ channel->GetAssociatedInterfaceSupport()->AddAssociatedInterface(
+ base::Bind(&ListenerWithSimpleAssociatedInterface::BindRequest,
+ base::Unretained(this)));
+ }
+
+ private:
+ // IPC::mojom::SimpleTestDriver:
+ void ExpectString(const mojo::String& str) override {
+ next_expected_string_ = str;
+ }
+
+ void RequestQuit(const RequestQuitCallback& callback) override {
+ EXPECT_EQ(kNumMessages, num_messages_received_);
+ received_quit_ = true;
+ callback.Run();
+ base::MessageLoop::current()->QuitWhenIdle();
+ }
+
+ void BindRequest(IPC::mojom::SimpleTestDriverAssociatedRequest request) {
+ DCHECK(!binding_.is_bound());
+ binding_.Bind(std::move(request));
+ }
+
+ std::string next_expected_string_;
+ int num_messages_received_ = 0;
+ bool received_quit_ = false;
+
+ mojo::AssociatedBinding<IPC::mojom::SimpleTestDriver> binding_;
+};
+
+const int ListenerWithSimpleAssociatedInterface::kNumMessages = 1000;
+
+class ListenerSendingAssociatedMessages : public IPC::Listener {
+ public:
+ ListenerSendingAssociatedMessages() {}
+
+ bool OnMessageReceived(const IPC::Message& message) override { return true; }
+
+ void OnChannelConnected(int32_t peer_pid) override {
+ DCHECK(channel_);
+ channel_->GetAssociatedInterfaceSupport()->GetRemoteAssociatedInterface(
+ &driver_);
+
+ // Send a bunch of interleaved messages, alternating between the associated
+ // interface and a legacy IPC::Message.
+ for (int i = 0; i < ListenerWithSimpleAssociatedInterface::kNumMessages;
+ ++i) {
+ std::string str = base::StringPrintf("Hello! %d", i);
+ driver_->ExpectString(str);
+ SendString(channel_, str);
+ }
+ driver_->RequestQuit(base::Bind(&OnQuitAck));
+ }
+
+ void set_channel(IPC::Channel* channel) { channel_ = channel; }
+
+ private:
+ static void OnQuitAck() { base::MessageLoop::current()->QuitWhenIdle(); }
+
+ IPC::Channel* channel_ = nullptr;
+ IPC::mojom::SimpleTestDriverAssociatedPtr driver_;
+};
+
+TEST_F(IPCChannelMojoTest, SimpleAssociatedInterface) {
+ InitWithMojo("SimpleAssociatedInterfaceClient");
+
+ ListenerWithSimpleAssociatedInterface listener;
+ CreateChannel(&listener);
+ ASSERT_TRUE(ConnectChannel());
+
+ listener.RegisterInterfaceFactory(channel());
+
+ base::RunLoop().Run();
+ channel()->Close();
+
+ EXPECT_TRUE(WaitForClientShutdown());
+ DestroyChannel();
+}
+
+DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT(SimpleAssociatedInterfaceClient,
+ ChannelClient) {
+ ListenerSendingAssociatedMessages listener;
+ Connect(&listener);
+ listener.set_channel(channel());
+
+ base::RunLoop().Run();
+
+ Close();
+}
+
#if defined(OS_POSIX)
class ListenerThatExpectsFile : public IPC::Listener {
public:
@@ -696,7 +813,7 @@
Close();
}
-#endif
+#endif // defined(OS_POSIX)
#if defined(OS_LINUX)
diff --git a/ipc/ipc_message_pipe_reader.cc b/ipc/ipc_message_pipe_reader.cc
index da2d85d..74c7285 100644
--- a/ipc/ipc_message_pipe_reader.cc
+++ b/ipc/ipc_message_pipe_reader.cc
@@ -115,6 +115,14 @@
return result == MOJO_RESULT_OK;
}
+void MessagePipeReader::GetRemoteInterface(
+ const std::string& name,
+ mojo::ScopedInterfaceEndpointHandle handle) {
+ mojom::GenericInterfaceAssociatedRequest request;
+ request.Bind(std::move(handle));
+ sender_->GetAssociatedInterface(name, std::move(request));
+}
+
void MessagePipeReader::Receive(
mojo::Array<uint8_t> data,
mojo::Array<mojom::SerializedHandlePtr> handles) {
@@ -138,6 +146,14 @@
delegate_->OnMessageReceived(message);
}
+void MessagePipeReader::GetAssociatedInterface(
+ const mojo::String& name,
+ mojom::GenericInterfaceAssociatedRequest request) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (delegate_)
+ delegate_->OnAssociatedInterfaceRequest(name, request.PassHandle());
+}
+
void MessagePipeReader::OnPipeError(MojoResult error) {
DCHECK(thread_checker_.CalledOnValidThread());
if (delegate_)
diff --git a/ipc/ipc_message_pipe_reader.h b/ipc/ipc_message_pipe_reader.h
index b15579d..5aec440 100644
--- a/ipc/ipc_message_pipe_reader.h
+++ b/ipc/ipc_message_pipe_reader.h
@@ -15,8 +15,10 @@
#include "base/macros.h"
#include "base/threading/thread_checker.h"
#include "ipc/ipc.mojom.h"
+#include "ipc/ipc_export.h"
#include "ipc/ipc_message.h"
#include "mojo/public/cpp/bindings/associated_binding.h"
+#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
#include "mojo/public/cpp/system/core.h"
#include "mojo/public/cpp/system/message_pipe.h"
@@ -41,12 +43,15 @@
// be called on any thread. All |Delegate| functions will be called on the IO
// thread.
//
-class MessagePipeReader : public mojom::Channel {
+class IPC_EXPORT MessagePipeReader : public NON_EXPORTED_BASE(mojom::Channel) {
public:
class Delegate {
public:
virtual void OnMessageReceived(const Message& message) = 0;
virtual void OnPipeError() = 0;
+ virtual void OnAssociatedInterfaceRequest(
+ const std::string& name,
+ mojo::ScopedInterfaceEndpointHandle handle) = 0;
};
// Delay the object deletion using the current message loop.
@@ -91,6 +96,10 @@
// thread.
bool Send(std::unique_ptr<Message> message);
+ // Requests an associated interface from the other end of the pipe.
+ void GetRemoteInterface(const std::string& name,
+ mojo::ScopedInterfaceEndpointHandle handle);
+
base::ProcessId GetPeerPid() const { return peer_pid_; }
protected:
@@ -101,6 +110,9 @@
// mojom::Channel:
void Receive(mojo::Array<uint8_t> data,
mojo::Array<mojom::SerializedHandlePtr> handles) override;
+ void GetAssociatedInterface(
+ const mojo::String& name,
+ mojom::GenericInterfaceAssociatedRequest request) override;
// |delegate_| is null once the message pipe is closed.
Delegate* delegate_;
diff --git a/ipc/ipc_mojo_bootstrap.cc b/ipc/ipc_mojo_bootstrap.cc
index dc8ffdc8..425f7948 100644
--- a/ipc/ipc_mojo_bootstrap.cc
+++ b/ipc/ipc_mojo_bootstrap.cc
@@ -591,6 +591,11 @@
endpoint_client_->set_connection_error_handler(handler);
}
+ mojo::AssociatedGroup* associated_group() {
+ DCHECK(controller_);
+ return controller_->associated_group();
+ }
+
void Bind(mojo::ScopedMessagePipeHandle handle) {
DCHECK(!controller_);
controller_ =
@@ -621,6 +626,9 @@
private:
// MojoBootstrap implementation.
void Connect() override;
+ mojo::AssociatedGroup* GetAssociatedGroup() override {
+ return bootstrap_.associated_group();
+ }
void OnInitDone(int32_t peer_pid);
@@ -680,6 +688,9 @@
private:
// MojoBootstrap implementation.
void Connect() override;
+ mojo::AssociatedGroup* GetAssociatedGroup() override {
+ return binding_.associated_group();
+ }
// mojom::Bootstrap implementation.
void Init(mojom::ChannelAssociatedRequest receive_channel,
diff --git a/ipc/ipc_mojo_bootstrap.h b/ipc/ipc_mojo_bootstrap.h
index 312003a..1d632d3 100644
--- a/ipc/ipc_mojo_bootstrap.h
+++ b/ipc/ipc_mojo_bootstrap.h
@@ -15,6 +15,7 @@
#include "ipc/ipc.mojom.h"
#include "ipc/ipc_channel.h"
#include "ipc/ipc_listener.h"
+#include "mojo/public/cpp/bindings/associated_group.h"
#include "mojo/public/cpp/system/message_pipe.h"
namespace IPC {
@@ -51,6 +52,8 @@
// Start the handshake over the underlying message pipe.
virtual void Connect() = 0;
+ virtual mojo::AssociatedGroup* GetAssociatedGroup() = 0;
+
// GetSelfPID returns our PID.
base::ProcessId GetSelfPID() const;
diff --git a/ipc/ipc_test.mojom b/ipc/ipc_test.mojom
new file mode 100644
index 0000000..9c1b30e
--- /dev/null
+++ b/ipc/ipc_test.mojom
@@ -0,0 +1,10 @@
+// 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.
+
+module IPC.mojom;
+
+interface SimpleTestDriver {
+ ExpectString(string str);
+ RequestQuit() => ();
+};