summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authorbrettw@chromium.org <brettw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-03-30 22:35:27 +0000
committerbrettw@chromium.org <brettw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-03-30 22:35:27 +0000
commit73d96dc3c451af3e4347d96471d8bdd02ab02853 (patch)
treef983e222b916a204bb4928e797b1061ee4185321 /chrome
parentc3f757f314b7a473e1b76bd2a3401d5599bebd7d (diff)
downloadchromium_src-73d96dc3c451af3e4347d96471d8bdd02ab02853.zip
chromium_src-73d96dc3c451af3e4347d96471d8bdd02ab02853.tar.gz
chromium_src-73d96dc3c451af3e4347d96471d8bdd02ab02853.tar.bz2
Initial implementation of an IPC adapter to expose Chrome IPC to Native Client.
This provides an implementation of sendmsg and recvmsg approxinately to what we think NaCl will expose to Chrome. Since NaCl isn't ready yet in this regard, it's still a bit speculative. And there is no support for sending handles across which will be the tricky part. TEST=included unit test BUG=none Review URL: https://chromiumcodereview.appspot.com/9863005 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@129981 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--chrome/nacl.gypi4
-rw-r--r--chrome/nacl/nacl_ipc_adapter.cc333
-rw-r--r--chrome/nacl/nacl_ipc_adapter.h143
-rw-r--r--chrome/nacl/nacl_ipc_adapter_unittest.cc289
-rw-r--r--chrome/nacl/nacl_ipc_manager.cc60
-rw-r--r--chrome/nacl/nacl_ipc_manager.h59
7 files changed, 889 insertions, 0 deletions
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index e180a03..573b005 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -2092,6 +2092,7 @@
'common/worker_thread_ticker_unittest.cc',
'common/zip_reader_unittest.cc',
'common/zip_unittest.cc',
+ 'nacl/nacl_ipc_adapter_unittest.cc',
'nacl/nacl_validation_query_unittest.cc',
'renderer/chrome_content_renderer_client_unittest.cc',
'renderer/content_settings_observer_unittest.cc',
diff --git a/chrome/nacl.gypi b/chrome/nacl.gypi
index f34c7bd..c9d2605 100644
--- a/chrome/nacl.gypi
+++ b/chrome/nacl.gypi
@@ -24,6 +24,10 @@
# .cc, .h, and .mm files under nacl that are used on all
# platforms, including both 32-bit and 64-bit Windows.
# Test files are also not included.
+ 'nacl/nacl_ipc_adapter.cc',
+ 'nacl/nacl_ipc_adapter.h',
+ 'nacl/nacl_ipc_manager.cc',
+ 'nacl/nacl_ipc_manager.h',
'nacl/nacl_main.cc',
'nacl/nacl_main_platform_delegate.h',
'nacl/nacl_main_platform_delegate_linux.cc',
diff --git a/chrome/nacl/nacl_ipc_adapter.cc b/chrome/nacl/nacl_ipc_adapter.cc
new file mode 100644
index 0000000..2b65157
--- /dev/null
+++ b/chrome/nacl/nacl_ipc_adapter.cc
@@ -0,0 +1,333 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/nacl/nacl_ipc_adapter.h"
+
+#include <limits.h>
+#include <string.h>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/memory/scoped_ptr.h"
+#include "build/build_config.h"
+
+namespace {
+
+enum BufferSizeStatus {
+ // The buffer contains a full message with no extra bytes.
+ MESSAGE_IS_COMPLETE,
+
+ // The message doesn't fit and the buffer contains only some of it.
+ MESSAGE_IS_TRUNCATED,
+
+ // The buffer contains a full message + extra data.
+ MESSAGE_HAS_EXTRA_DATA
+};
+
+BufferSizeStatus GetBufferStatus(const char* data, size_t len) {
+ if (len < sizeof(NaClIPCAdapter::NaClMessageHeader))
+ return MESSAGE_IS_TRUNCATED;
+
+ const NaClIPCAdapter::NaClMessageHeader* header =
+ reinterpret_cast<const NaClIPCAdapter::NaClMessageHeader*>(data);
+ uint32 message_size =
+ sizeof(NaClIPCAdapter::NaClMessageHeader) + header->payload_size;
+
+ if (len == message_size)
+ return MESSAGE_IS_COMPLETE;
+ if (len > message_size)
+ return MESSAGE_HAS_EXTRA_DATA;
+ return MESSAGE_IS_TRUNCATED;
+}
+
+} // namespace
+
+class NaClIPCAdapter::RewrittenMessage
+ : public base::RefCounted<RewrittenMessage> {
+ public:
+ RewrittenMessage();
+
+ bool is_consumed() const { return data_read_cursor_ == data_len_; }
+
+ void SetData(const NaClIPCAdapter::NaClMessageHeader& header,
+ const void* payload, size_t payload_length);
+
+ int Read(char* dest_buffer, int dest_buffer_size);
+
+ private:
+ scoped_array<char> data_;
+ int data_len_;
+
+ // Offset into data where the next read will happen. This will be equal to
+ // data_len_ when all data has been consumed.
+ int data_read_cursor_;
+};
+
+NaClIPCAdapter::RewrittenMessage::RewrittenMessage()
+ : data_len_(0),
+ data_read_cursor_(0) {
+}
+
+void NaClIPCAdapter::RewrittenMessage::SetData(
+ const NaClIPCAdapter::NaClMessageHeader& header,
+ const void* payload,
+ size_t payload_length) {
+ DCHECK(!data_.get() && data_len_ == 0);
+ int header_len = sizeof(NaClIPCAdapter::NaClMessageHeader);
+ data_len_ = header_len + static_cast<int>(payload_length);
+ data_.reset(new char[data_len_]);
+
+ memcpy(data_.get(), &header, sizeof(NaClIPCAdapter::NaClMessageHeader));
+ memcpy(&data_[header_len], payload, payload_length);
+}
+
+int NaClIPCAdapter::RewrittenMessage::Read(char* dest_buffer,
+ int dest_buffer_size) {
+ int bytes_to_write = std::min(dest_buffer_size,
+ data_len_ - data_read_cursor_);
+ if (bytes_to_write == 0)
+ return 0;
+
+ memcpy(dest_buffer, &data_[data_read_cursor_], bytes_to_write);
+ data_read_cursor_ += bytes_to_write;
+ return bytes_to_write;
+}
+
+NaClIPCAdapter::LockedData::LockedData() : channel_closed_(false) {
+}
+
+NaClIPCAdapter::LockedData::~LockedData() {
+}
+
+NaClIPCAdapter::IOThreadData::IOThreadData() {
+}
+
+NaClIPCAdapter::IOThreadData::~IOThreadData() {
+}
+
+NaClIPCAdapter::NaClIPCAdapter(const IPC::ChannelHandle& handle,
+ base::TaskRunner* runner)
+ : lock_(),
+ cond_var_(&lock_),
+ task_runner_(runner),
+ locked_data_() {
+ io_thread_data_.channel_.reset(
+ new IPC::Channel(handle, IPC::Channel::MODE_SERVER, this));
+}
+
+NaClIPCAdapter::NaClIPCAdapter(scoped_ptr<IPC::Channel> channel,
+ base::TaskRunner* runner)
+ : lock_(),
+ cond_var_(&lock_),
+ task_runner_(runner),
+ locked_data_() {
+ io_thread_data_.channel_ = channel.Pass();
+}
+
+NaClIPCAdapter::~NaClIPCAdapter() {
+}
+
+// Note that this message is controlled by the untrusted code. So we should be
+// skeptical of anything it contains and quick to give up if anything is fishy.
+int NaClIPCAdapter::Send(const char* input_data, size_t input_data_len) {
+ base::AutoLock lock(lock_);
+
+ if (input_data_len > IPC::Channel::kMaximumMessageSize) {
+ ClearToBeSent();
+ return -1;
+ }
+
+ // current_message[_len] refers to the total input data received so far.
+ const char* current_message;
+ size_t current_message_len;
+ bool did_append_input_data;
+ if (locked_data_.to_be_sent_.empty()) {
+ // No accumulated data, we can avoid a copy by referring to the input
+ // buffer (the entire message fitting in one call is the common case).
+ current_message = input_data;
+ current_message_len = input_data_len;
+ did_append_input_data = false;
+ } else {
+ // We've already accumulated some data, accumulate this new data and
+ // point to the beginning of the buffer.
+
+ // Make sure our accumulated message size doesn't overflow our max. Since
+ // we know that data_len < max size (checked above) and our current
+ // accumulated value is also < max size, we just need to make sure that
+ // 2x max size can never overflow.
+ COMPILE_ASSERT(IPC::Channel::kMaximumMessageSize < (UINT_MAX / 2),
+ MaximumMessageSizeWillOverflow);
+ size_t new_size = locked_data_.to_be_sent_.size() + input_data_len;
+ if (new_size > IPC::Channel::kMaximumMessageSize) {
+ ClearToBeSent();
+ return -1;
+ }
+
+ locked_data_.to_be_sent_.append(input_data, input_data_len);
+ current_message = &locked_data_.to_be_sent_[0];
+ current_message_len = locked_data_.to_be_sent_.size();
+ did_append_input_data = true;
+ }
+
+ // Check the total data we've accumulated so far to see if it contains a full
+ // message.
+ switch (GetBufferStatus(current_message, current_message_len)) {
+ case MESSAGE_IS_COMPLETE: {
+ // Got a complete message, can send it out. This will be the common case.
+ bool success = SendCompleteMessage(current_message, current_message_len);
+ ClearToBeSent();
+ return success ? static_cast<int>(input_data_len) : -1;
+ }
+ case MESSAGE_IS_TRUNCATED:
+ // For truncated messages, just accumulate the new data (if we didn't
+ // already do so above) and go back to waiting for more.
+ if (!did_append_input_data)
+ locked_data_.to_be_sent_.append(input_data, input_data_len);
+ return static_cast<int>(input_data_len);
+ case MESSAGE_HAS_EXTRA_DATA:
+ default:
+ // When the plugin gives us too much data, it's an error.
+ ClearToBeSent();
+ return -1;
+ }
+}
+
+int NaClIPCAdapter::BlockingReceive(char* output_buffer,
+ int output_buffer_size) {
+ int retval = 0;
+ {
+ base::AutoLock lock(lock_);
+ while (locked_data_.to_be_received_.empty() &&
+ !locked_data_.channel_closed_)
+ cond_var_.Wait();
+ if (locked_data_.channel_closed_) {
+ retval = -1;
+ } else {
+ retval = LockedReceive(output_buffer, output_buffer_size);
+ DCHECK(retval > 0);
+ }
+ }
+ cond_var_.Signal();
+ return retval;
+}
+
+void NaClIPCAdapter::CloseChannel() {
+ {
+ base::AutoLock lock(lock_);
+ locked_data_.channel_closed_ = true;
+ }
+ cond_var_.Signal();
+
+ task_runner_->PostTask(FROM_HERE,
+ base::Bind(&NaClIPCAdapter::CloseChannelOnIOThread, this));
+}
+
+bool NaClIPCAdapter::OnMessageReceived(const IPC::Message& message) {
+ {
+ base::AutoLock lock(lock_);
+
+ // There is some padding in this structure (the "padding" member is 16
+ // bits but this then gets padded to 32 bits). We want to be sure not to
+ // leak data to the untrusted plugin, so zero everything out first.
+ NaClMessageHeader header;
+ memset(&header, 0, sizeof(NaClMessageHeader));
+
+ header.payload_size = static_cast<uint32>(message.payload_size());
+ header.routing = message.routing_id();
+ header.type = message.type();
+ header.flags = message.flags();
+ header.num_fds = 0; // TODO(brettw) hook this up.
+
+ scoped_refptr<RewrittenMessage> dest(new RewrittenMessage);
+ dest->SetData(header, message.payload(), message.payload_size());
+ locked_data_.to_be_received_.push(dest);
+ }
+ cond_var_.Signal();
+ return true;
+}
+
+void NaClIPCAdapter::OnChannelConnected(int32 peer_pid) {
+}
+
+void NaClIPCAdapter::OnChannelError() {
+ CloseChannel();
+}
+
+int NaClIPCAdapter::LockedReceive(char* output_buffer, int output_buffer_size) {
+ lock_.AssertAcquired();
+
+ if (locked_data_.to_be_received_.empty())
+ return 0;
+ scoped_refptr<RewrittenMessage> current =
+ locked_data_.to_be_received_.front();
+
+ int retval = current->Read(output_buffer, output_buffer_size);
+
+ // When a message is entirely consumed, remove if from the waiting queue.
+ if (current->is_consumed())
+ locked_data_.to_be_received_.pop();
+ return retval;
+}
+
+bool NaClIPCAdapter::SendCompleteMessage(const char* buffer,
+ size_t buffer_len) {
+ // The message will have already been validated, so we know it's large enough
+ // for our header.
+ const NaClMessageHeader* header =
+ reinterpret_cast<const NaClMessageHeader*>(buffer);
+
+ // Length of the message not including the body. The data passed to us by the
+ // plugin should match that in the message header. This should have already
+ // been validated by GetBufferStatus.
+ int body_len = static_cast<int>(buffer_len - sizeof(NaClMessageHeader));
+ DCHECK(body_len == static_cast<int>(header->payload_size));
+
+ // We actually discard the flags and only copy the ones we care about. This
+ // is just because message doesn't have a constructor that takes raw flags.
+ scoped_ptr<IPC::Message> msg(
+ new IPC::Message(header->routing, header->type,
+ IPC::Message::PRIORITY_NORMAL));
+ if (header->flags & IPC::Message::SYNC_BIT)
+ msg->set_sync();
+ if (header->flags & IPC::Message::REPLY_BIT)
+ msg->set_reply();
+ if (header->flags & IPC::Message::REPLY_ERROR_BIT)
+ msg->set_reply_error();
+ if (header->flags & IPC::Message::UNBLOCK_BIT)
+ msg->set_unblock(true);
+
+ msg->WriteBytes(&buffer[sizeof(NaClMessageHeader)], body_len);
+
+ // Technically we didn't have to do any of the previous work in the lock. But
+ // sometimes our buffer will point to the to_be_sent_ string which is
+ // protected by the lock, and it's messier to factor Send() such that it can
+ // unlock for us. Holding the lock for the message construction, which is
+ // just some memcpys, shouldn't be a big deal.
+ lock_.AssertAcquired();
+ if (locked_data_.channel_closed_)
+ return false; // TODO(brettw) clean up handles here when we add support!
+
+ // Actual send must be done on the I/O thread.
+ task_runner_->PostTask(FROM_HERE,
+ base::Bind(&NaClIPCAdapter::SendMessageOnIOThread, this,
+ base::Passed(&msg)));
+ return true;
+}
+
+void NaClIPCAdapter::CloseChannelOnIOThread() {
+ io_thread_data_.channel_->Close();
+}
+
+void NaClIPCAdapter::SendMessageOnIOThread(scoped_ptr<IPC::Message> message) {
+ io_thread_data_.channel_->Send(message.release());
+}
+
+void NaClIPCAdapter::ClearToBeSent() {
+ lock_.AssertAcquired();
+
+ // Don't let the string keep its buffer behind our back.
+ std::string empty;
+ locked_data_.to_be_sent_.swap(empty);
+}
diff --git a/chrome/nacl/nacl_ipc_adapter.h b/chrome/nacl/nacl_ipc_adapter.h
new file mode 100644
index 0000000..0949755
--- /dev/null
+++ b/chrome/nacl/nacl_ipc_adapter.h
@@ -0,0 +1,143 @@
+// Copyright (c) 2012 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 CHROME_NACL_NACL_IPC_ADAPTER_H_
+#define CHROME_NACL_NACL_IPC_ADAPTER_H_
+#pragma once
+
+#include <queue>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "base/task_runner.h"
+#include "ipc/ipc_channel.h"
+
+// Adapts a Chrome IPC channel to an IPC channel that we expose to Native
+// Client. This provides a mapping in both directions, so when IPC messages
+// come in from another process, we rewrite them and allow them to be received
+// via a recvmsg-like interface in the NaCl code. When NaCl code calls sendmsg,
+// we implement that as sending IPC messages on the channel.
+//
+// This object also provides the necessary logic for rewriting IPC messages.
+// NaCl code is platform-independent and runs in a Posix-like enviroment, but
+// some formatting in the message and the way handles are transferred varies
+// by platform. This class bridges that gap to provide what looks like a
+// normal platform-specific IPC implementation to Chrome, and a Posix-like
+// version on every platform to NaCl.
+//
+// This object must be threadsafe since the nacl environment determines which
+// thread every function is called on.
+class NaClIPCAdapter : public base::RefCountedThreadSafe<NaClIPCAdapter>,
+ public IPC::Channel::Listener {
+ public:
+ // Chrome's IPC message format varies by platform, NaCl's does not. In
+ // particular, the header has some extra fields on Posix platforms. Since
+ // NaCl is a Posix environment, it gets that version of the header. This
+ // header is duplicated here so we have a cross-platform definition of the
+ // header we're exposing to NaCl.
+#pragma pack(push, 4)
+ struct NaClMessageHeader : public Pickle::Header {
+ int32 routing;
+ uint32 type;
+ uint32 flags;
+ uint16 num_fds;
+ uint16 pad;
+ };
+#pragma pack(pop)
+
+ // Creates an adapter, using the thread associated with the given task
+ // runner for posting messages. In normal use, the task runner will post to
+ // the I/O thread of the process.
+ NaClIPCAdapter(const IPC::ChannelHandle& handle, base::TaskRunner* runner);
+
+ // Initializes with a given channel that's already created for testing
+ // purposes. This function will take ownership of the given channel.
+ NaClIPCAdapter(scoped_ptr<IPC::Channel> channel, base::TaskRunner* runner);
+
+ virtual ~NaClIPCAdapter();
+
+ // Implementation of sendmsg. Returns the number of bytes written or -1 on
+ // failure.
+ int Send(const char* input_data, size_t input_data_len);
+
+ // Implementation of recvmsg. Returns the number of bytes read or -1 on
+ // failure. This will block until there's an error or there is data to
+ // read.
+ int BlockingReceive(char* output_buffer, int output_buffer_size);
+
+ // Closes the IPC channel.
+ void CloseChannel();
+
+ // Listener implementation.
+ virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
+ virtual void OnChannelConnected(int32 peer_pid) OVERRIDE;
+ virtual void OnChannelError() OVERRIDE;
+
+ private:
+ class RewrittenMessage;
+
+ // This is the data that must only be accessed inside the lock. This struct
+ // just separates it so it's easier to see.
+ struct LockedData {
+ LockedData();
+ ~LockedData();
+
+ // Messages that we have read off of the Chrome IPC channel that are waiting
+ // to be received by the plugin.
+ std::queue< scoped_refptr<RewrittenMessage> > to_be_received_;
+
+ // Data that we've queued from the plugin to send, but doesn't consist of a
+ // full message yet. The calling code can break apart the message into
+ // smaller pieces, and we need to send the message to the other process in
+ // one chunk.
+ //
+ // The IPC channel always starts a new send() at the beginning of each
+ // message, so we don't need to worry about arbitrary message boundaries.
+ std::string to_be_sent_;
+
+ bool channel_closed_;
+ };
+
+ // This is the data that must only be accessed on the I/O thread (as defined
+ // by TaskRunner). This struct just separates it so it's easier to see.
+ struct IOThreadData {
+ IOThreadData();
+ ~IOThreadData();
+
+ scoped_ptr<IPC::Channel> channel_;
+ };
+
+ // Reads up to the given amount of data. Returns 0 if nothing is waiting.
+ int LockedReceive(char* output_buffer, int output_buffer_size);
+
+ // Sends a message that we know has been completed to the Chrome process.
+ bool SendCompleteMessage(const char* buffer, size_t buffer_len);
+
+ // Clears the LockedData.to_be_sent_ structure in a way to make sure that
+ // the memory is deleted. std::string can sometimes hold onto the buffer
+ // for future use which we don't want.
+ void ClearToBeSent();
+
+ void CloseChannelOnIOThread();
+ void SendMessageOnIOThread(scoped_ptr<IPC::Message> message);
+
+ base::Lock lock_;
+ base::ConditionVariable cond_var_;
+
+ scoped_refptr<base::TaskRunner> task_runner_;
+
+ // To be accessed inside of lock_ only.
+ LockedData locked_data_;
+
+ // To be accessed on the I/O thread (via task runner) only.
+ IOThreadData io_thread_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(NaClIPCAdapter);
+};
+
+#endif // CHROME_NACL_NACL_IPC_ADAPTER_H_
diff --git a/chrome/nacl/nacl_ipc_adapter_unittest.cc b/chrome/nacl/nacl_ipc_adapter_unittest.cc
new file mode 100644
index 0000000..33bb917
--- /dev/null
+++ b/chrome/nacl/nacl_ipc_adapter_unittest.cc
@@ -0,0 +1,289 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/nacl/nacl_ipc_adapter.h"
+
+#include <string.h>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop.h"
+#include "base/message_loop_proxy.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/simple_thread.h"
+#include "ipc/ipc_test_sink.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class NaClIPCAdapterTest : public testing::Test {
+ public:
+ NaClIPCAdapterTest() {}
+
+ // testing::Test implementation.
+ virtual void SetUp() OVERRIDE {
+ sink_ = new IPC::TestSink;
+
+ // Takes ownership of the sink_ pointer.
+ adapter_ = new NaClIPCAdapter(scoped_ptr<IPC::Channel>(sink_),
+ base::MessageLoopProxy::current());
+ }
+ virtual void TearDown() OVERRIDE {
+ sink_ = NULL; // This pointer is actually owned by the IPCAdapter.
+ adapter_ = NULL;
+ }
+
+ protected:
+ MessageLoop message_loop_;
+
+ scoped_refptr<NaClIPCAdapter> adapter_;
+
+ // Messages sent from nacl to the adapter end up here. Note that we create
+ // this pointer and pass ownership of it to the IPC adapter, who will keep
+ // it alive as long as the adapter is alive. This means that when the
+ // adapter goes away, this pointer will become invalid.
+ //
+ // In real life the adapter needs to take ownership so the channel can be
+ // destroyed on the right thread.
+ IPC::TestSink* sink_;
+};
+
+} // namespace
+
+// Tests a simple message getting rewritten sent from native code to NaCl.
+TEST_F(NaClIPCAdapterTest, SimpleReceiveRewriting) {
+ int routing_id = 0x89898989;
+ uint32 type = 0x55555555;
+ IPC::Message input(routing_id, type, IPC::Message::PRIORITY_NORMAL);
+
+ int value = 0x12345678;
+ input.WriteInt(value);
+ adapter_->OnMessageReceived(input);
+
+ // Buffer just need to be big enough for our message with one int.
+ const int kBufSize = 64;
+ char buf[kBufSize];
+
+ int bytes_read = adapter_->BlockingReceive(buf, kBufSize);
+ EXPECT_EQ(sizeof(NaClIPCAdapter::NaClMessageHeader) + sizeof(int),
+ static_cast<size_t>(bytes_read));
+
+ const NaClIPCAdapter::NaClMessageHeader* output_header =
+ reinterpret_cast<const NaClIPCAdapter::NaClMessageHeader*>(buf);
+ EXPECT_EQ(sizeof(int), output_header->payload_size);
+ EXPECT_EQ(routing_id, output_header->routing);
+ EXPECT_EQ(type, output_header->type);
+ EXPECT_EQ(static_cast<uint32>(IPC::Message::PRIORITY_NORMAL),
+ output_header->flags);
+ EXPECT_EQ(0u, output_header->num_fds);
+ EXPECT_EQ(0u, output_header->pad);
+
+ // Validate the payload.
+ EXPECT_EQ(value,
+ *reinterpret_cast<const int*>(&buf[
+ sizeof(NaClIPCAdapter::NaClMessageHeader)]));
+}
+
+// Tests a simple message getting rewritten sent from NaCl to native code.
+TEST_F(NaClIPCAdapterTest, SendRewriting) {
+ int routing_id = 0x89898989;
+ uint32 type = 0x55555555;
+ int value = 0x12345678;
+
+ // Send a message with one int inside it.
+ const int buf_size = sizeof(NaClIPCAdapter::NaClMessageHeader) + sizeof(int);
+ char buf[buf_size] = {0};
+
+ NaClIPCAdapter::NaClMessageHeader* header =
+ reinterpret_cast<NaClIPCAdapter::NaClMessageHeader*>(buf);
+ header->payload_size = sizeof(int);
+ header->routing = routing_id;
+ header->type = type;
+ header->flags = 0;
+ header->num_fds = 0;
+ *reinterpret_cast<int*>(
+ &buf[sizeof(NaClIPCAdapter::NaClMessageHeader)]) = value;
+
+ int result = adapter_->Send(buf, buf_size);
+ EXPECT_EQ(buf_size, result);
+
+ // Check that the message came out the other end in the test sink
+ // (messages are posted to we have to pump).
+ message_loop_.RunAllPending();
+ ASSERT_EQ(1u, sink_->message_count());
+ const IPC::Message* msg = sink_->GetMessageAt(0);
+
+ EXPECT_EQ(sizeof(int), msg->payload_size());
+ EXPECT_EQ(header->routing, msg->routing_id());
+ EXPECT_EQ(header->type, msg->type());
+
+ // Now test the partial send case. We should be able to break the message
+ // into two parts and it should still work.
+ sink_->ClearMessages();
+ int first_chunk_size = 7;
+ result = adapter_->Send(buf, first_chunk_size);
+ EXPECT_EQ(first_chunk_size, result);
+
+ // First partial send should not have made any messages.
+ message_loop_.RunAllPending();
+ ASSERT_EQ(0u, sink_->message_count());
+
+ // Second partial send should do the same.
+ int second_chunk_size = 2;
+ result = adapter_->Send(&buf[first_chunk_size], second_chunk_size);
+ EXPECT_EQ(second_chunk_size, result);
+ message_loop_.RunAllPending();
+ ASSERT_EQ(0u, sink_->message_count());
+
+ // Send the rest of the message in a third chunk.
+ int third_chunk_size = buf_size - first_chunk_size - second_chunk_size;
+ result = adapter_->Send(&buf[first_chunk_size + second_chunk_size],
+ third_chunk_size);
+ EXPECT_EQ(third_chunk_size, result);
+
+ // Last send should have generated one message.
+ message_loop_.RunAllPending();
+ ASSERT_EQ(1u, sink_->message_count());
+ msg = sink_->GetMessageAt(0);
+ EXPECT_EQ(sizeof(int), msg->payload_size());
+ EXPECT_EQ(header->routing, msg->routing_id());
+ EXPECT_EQ(header->type, msg->type());
+}
+
+// Tests when a buffer is too small to receive the entire message.
+TEST_F(NaClIPCAdapterTest, PartialReceive) {
+ int routing_id_1 = 0x89898989;
+ uint32 type_1 = 0x55555555;
+ IPC::Message input_1(routing_id_1, type_1, IPC::Message::PRIORITY_NORMAL);
+ int value_1 = 0x12121212;
+ input_1.WriteInt(value_1);
+ adapter_->OnMessageReceived(input_1);
+
+ int routing_id_2 = 0x90909090;
+ uint32 type_2 = 0x66666666;
+ IPC::Message input_2(routing_id_2, type_2, IPC::Message::PRIORITY_NORMAL);
+ int value_2 = 0x23232323;
+ input_2.WriteInt(value_2);
+ adapter_->OnMessageReceived(input_2);
+
+ const int kBufSize = 64;
+ char buf[kBufSize];
+
+ // Read part of the first message.
+ int bytes_requested = 7;
+ int bytes_read = adapter_->BlockingReceive(buf, bytes_requested);
+ ASSERT_EQ(bytes_requested, bytes_read);
+
+ // Read the rest, this should give us the rest of the first message only.
+ bytes_read += adapter_->BlockingReceive(&buf[bytes_requested],
+ kBufSize - bytes_requested);
+ EXPECT_EQ(sizeof(NaClIPCAdapter::NaClMessageHeader) + sizeof(int),
+ static_cast<size_t>(bytes_read));
+
+ // Make sure we got the right message.
+ const NaClIPCAdapter::NaClMessageHeader* output_header =
+ reinterpret_cast<const NaClIPCAdapter::NaClMessageHeader*>(buf);
+ EXPECT_EQ(sizeof(int), output_header->payload_size);
+ EXPECT_EQ(routing_id_1, output_header->routing);
+ EXPECT_EQ(type_1, output_header->type);
+
+ // Read the second message to make sure we went on to it.
+ bytes_read = adapter_->BlockingReceive(buf, kBufSize);
+ EXPECT_EQ(sizeof(NaClIPCAdapter::NaClMessageHeader) + sizeof(int),
+ static_cast<size_t>(bytes_read));
+ output_header =
+ reinterpret_cast<const NaClIPCAdapter::NaClMessageHeader*>(buf);
+ EXPECT_EQ(sizeof(int), output_header->payload_size);
+ EXPECT_EQ(routing_id_2, output_header->routing);
+ EXPECT_EQ(type_2, output_header->type);
+}
+
+// Tests sending messages that are too large. We test sends that are too
+// small implicitly here and in the success case because in that case it
+// succeeds and buffers the data.
+TEST_F(NaClIPCAdapterTest, SendOverflow) {
+ int routing_id = 0x89898989;
+ uint32 type = 0x55555555;
+ int value = 0x12345678;
+
+ // Make a message with one int inside it. Reserve some extra space so
+ // we can test what happens when we send too much data.
+ const int buf_size = sizeof(NaClIPCAdapter::NaClMessageHeader) + sizeof(int);
+ const int big_buf_size = buf_size + 4;
+ char buf[big_buf_size] = {0};
+
+ NaClIPCAdapter::NaClMessageHeader* header =
+ reinterpret_cast<NaClIPCAdapter::NaClMessageHeader*>(buf);
+ header->payload_size = sizeof(int);
+ header->routing = routing_id;
+ header->type = type;
+ header->flags = 0;
+ header->num_fds = 0;
+ *reinterpret_cast<int*>(
+ &buf[sizeof(NaClIPCAdapter::NaClMessageHeader)]) = value;
+
+ // Send too much data and make sure that the send fails.
+ int result = adapter_->Send(buf, big_buf_size);
+ EXPECT_EQ(-1, result);
+ message_loop_.RunAllPending();
+ ASSERT_EQ(0u, sink_->message_count());
+
+ // Send too much data in two chunks and make sure that the send fails.
+ int first_chunk_size = 7;
+ result = adapter_->Send(buf, first_chunk_size);
+ EXPECT_EQ(first_chunk_size, result);
+
+ // First partial send should not have made any messages.
+ message_loop_.RunAllPending();
+ ASSERT_EQ(0u, sink_->message_count());
+
+ int second_chunk_size = big_buf_size - first_chunk_size;
+ result = adapter_->Send(&buf[first_chunk_size], second_chunk_size);
+ EXPECT_EQ(-1, result);
+ message_loop_.RunAllPending();
+ ASSERT_EQ(0u, sink_->message_count());
+}
+
+// Tests that when the IPC channel reports an error, that waiting reads are
+// unblocked and return a -1 error code.
+TEST_F(NaClIPCAdapterTest, ReadWithChannelError) {
+ // Have a background thread that waits a bit and calls the channel error
+ // handler. This should wake up any waiting threads and immediately return
+ // -1. There is an inherent race condition in that we can't be sure if the
+ // other thread is actually waiting when this happens. This is OK, since the
+ // behavior (which we also explicitly test later) is to return -1 if the
+ // channel has already had an error when you start waiting.
+ class MyThread : public base::SimpleThread {
+ public:
+ explicit MyThread(NaClIPCAdapter* adapter)
+ : SimpleThread("NaClIPCAdapterThread"),
+ adapter_(adapter) {}
+ virtual void Run() {
+ base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1));
+ adapter_->OnChannelError();
+ }
+ private:
+ scoped_refptr<NaClIPCAdapter> adapter_;
+ };
+ MyThread thread(adapter_);
+
+ // IMPORTANT: do not return early from here down (including ASSERT_*) because
+ // the thread needs to joined or it will assert.
+ thread.Start();
+
+ // Request data. This will normally (modulo races) block until data is
+ // received or there is an error, and the thread above will wake us up
+ // after 1s.
+ const int kBufSize = 64;
+ char buf[kBufSize];
+ int result = adapter_->BlockingReceive(buf, kBufSize);
+ EXPECT_EQ(-1, result);
+
+ // Test the "previously had an error" case. BlockingReceive should return
+ // immediately if there was an error.
+ result = adapter_->BlockingReceive(buf, kBufSize);
+ EXPECT_EQ(-1, result);
+
+ thread.Join();
+}
+
diff --git a/chrome/nacl/nacl_ipc_manager.cc b/chrome/nacl/nacl_ipc_manager.cc
new file mode 100644
index 0000000..00e132a
--- /dev/null
+++ b/chrome/nacl/nacl_ipc_manager.cc
@@ -0,0 +1,60 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/nacl/nacl_ipc_manager.h"
+
+#include "chrome/nacl/nacl_ipc_adapter.h"
+#include "content/common/child_process.h"
+
+NaClIPCManager::NaClIPCManager() {
+}
+
+NaClIPCManager::~NaClIPCManager() {
+}
+
+void* NaClIPCManager::CreateChannel(const IPC::ChannelHandle& handle) {
+ scoped_refptr<NaClIPCAdapter> adapter(
+ new NaClIPCAdapter(handle,
+ ChildProcess::current()->io_message_loop_proxy()));
+
+ // Use the object's address as the handle given to nacl. We just need a
+ // unique void* to give to nacl for us to look it up when we get calls on
+ // this handle in the future.
+ void* nacl_handle = adapter.get();
+ adapters_.insert(std::make_pair(nacl_handle, adapter));
+ return nacl_handle;
+}
+
+void NaClIPCManager::DestroyChannel(void* handle) {
+ scoped_refptr<NaClIPCAdapter> adapter = GetAdapter(handle);
+ if (!adapter.get())
+ return;
+ adapter->CloseChannel();
+}
+
+int NaClIPCManager::SendMessage(void* handle,
+ const char* data,
+ int data_length) {
+ scoped_refptr<NaClIPCAdapter> adapter = GetAdapter(handle);
+ if (!adapter.get())
+ return -1;
+ return adapter->Send(data, data_length);
+}
+
+int NaClIPCManager::ReceiveMessage(void* handle,
+ char* buffer,
+ int buffer_length) {
+ scoped_refptr<NaClIPCAdapter> adapter = GetAdapter(handle);
+ if (!adapter.get())
+ return -1;
+ return adapter->BlockingReceive(buffer, buffer_length);
+}
+
+scoped_refptr<NaClIPCAdapter> NaClIPCManager::GetAdapter(void* handle) {
+ base::AutoLock lock(lock_);
+ AdapterMap::iterator found = adapters_.find(handle);
+ if (found == adapters_.end())
+ return scoped_refptr<NaClIPCAdapter>();
+ return found->second;
+}
diff --git a/chrome/nacl/nacl_ipc_manager.h b/chrome/nacl/nacl_ipc_manager.h
new file mode 100644
index 0000000..603b8dc
--- /dev/null
+++ b/chrome/nacl/nacl_ipc_manager.h
@@ -0,0 +1,59 @@
+// Copyright (c) 2012 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 CHROME_NACL_NACL_IPC_MANAGER_H_
+#define CHROME_NACL_NACL_IPC_MANAGER_H_
+#pragma once
+
+#include <map>
+
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#include "ipc/ipc_channel_handle.h"
+
+class NaClIPCAdapter;
+
+// This class manages all IPC channels exposed to NaCl. We give NaCl void*
+// "handles" to identify channels. When the untrusted nacl code does operations
+// on these handles, this class maps them to the corresponding adapter object.
+//
+// This class must be threadsafe since nacl send/recvmsg functions can be
+// called on any thread.
+class NaClIPCManager {
+ public:
+ NaClIPCManager();
+ ~NaClIPCManager();
+
+ // Creates a nacl channel associated with the given channel handle (normally
+ // this will come from the browser process). Returns the handle that should
+ // be given to nacl to associated with this IPC channel.
+ void* CreateChannel(const IPC::ChannelHandle& handle);
+
+ // Destroys the channel with the given handle.
+ void DestroyChannel(void* handle);
+
+ // Implementation of sendmsg on the given channel. The return value is the
+ // number of bytes written or -1 on failure.
+ int SendMessage(void* handle, const char* data, int data_length);
+
+ // Implementation of recvmsg on the given channel. The return value is the
+ // number of bytes received or -1 on failure.
+ int ReceiveMessage(void* handle, char* buffer, int buffer_length);
+
+ private:
+ // Looks up the adapter if given a handle. The pointer wil be null on
+ // failures.
+ scoped_refptr<NaClIPCAdapter> GetAdapter(void* handle);
+
+ // Lock around all data below.
+ base::Lock lock_;
+
+ // All active IPC channels. Locked by lock_ above.
+ typedef std::map<void*, scoped_refptr<NaClIPCAdapter> > AdapterMap;
+ AdapterMap adapters_;
+
+ DISALLOW_COPY_AND_ASSIGN(NaClIPCManager);
+};
+
+#endif // CHROME_NACL_NACL_IPC_MANAGER_H_