diff options
Diffstat (limited to 'chrome')
-rw-r--r-- | chrome/chrome_tests.gypi | 1 | ||||
-rw-r--r-- | chrome/nacl.gypi | 4 | ||||
-rw-r--r-- | chrome/nacl/nacl_ipc_adapter.cc | 333 | ||||
-rw-r--r-- | chrome/nacl/nacl_ipc_adapter.h | 143 | ||||
-rw-r--r-- | chrome/nacl/nacl_ipc_adapter_unittest.cc | 289 | ||||
-rw-r--r-- | chrome/nacl/nacl_ipc_manager.cc | 60 | ||||
-rw-r--r-- | chrome/nacl/nacl_ipc_manager.h | 59 |
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_
|