Chromium Code Reviews| Index: chrome/nacl/nacl_ipc_adapter.cc |
| diff --git a/chrome/nacl/nacl_ipc_adapter.cc b/chrome/nacl/nacl_ipc_adapter.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..f5720815d8a588d7cc23671372fae30b407a3a36 |
| --- /dev/null |
| +++ b/chrome/nacl/nacl_ipc_adapter.cc |
| @@ -0,0 +1,325 @@ |
| +// 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 { |
| + |
| +// The NaCl message header type is always like Posix. So when we're compiling |
| +// on Posix platforms, we can check that our header is correct. |
| +#if defined(OS_POSIX) |
| +COMPILE_ASSERT(sizeof(NaClIPCAdapter::NaClMessageHeader) == |
| + sizeof(IPC::Message::Header), |
| + PosixHeaderSizesDontMatch); |
| +#endif |
| + |
| +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. |
| + BUFFER_UNDERFLOW |
| +}; |
| + |
| +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 BUFFER_UNDERFLOW; |
| + 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::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; |
| + } |
| + |
| + const char* current_message = input_data; |
| + size_t current_message_len = input_data_len; |
| + if (!locked_data_.to_be_sent_.empty()) { |
| + // We've already accumulated some data. |
| + |
| + // 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(); |
| + } |
| + |
| + // Nothing accumulated, so see if the input data contains a full message. |
|
bbudge
2012/03/26 22:52:30
Something seems wrong here. The above 'if' block w
brettw
2012/03/27 17:40:27
The comment was misleading but I think the code wa
|
| + // This will be the common case. |
| + switch (GetBufferStatus(current_message, current_message_len)) { |
| + case MESSAGE_IS_COMPLETE: { |
| + // Got a complete message, can send it out. |
| + bool success = SendCompleteMessage(current_message, current_message_len); |
| + ClearToBeSent(); |
| + return success ? input_data_len : -1; |
| + } |
| + case MESSAGE_IS_TRUNCATED: |
| + // For truncated messages, just accumulate the new data and go back to |
| + // waiting for more. |
| + locked_data_.to_be_sent_.append(input_data, input_data_len); |
|
bbudge
2012/03/26 22:52:30
Seems like data could get added twice.
brettw
2012/03/27 17:40:27
Whoops! Nice catch. I modified the test to send in
|
| + return input_data_len; |
| + case BUFFER_UNDERFLOW: |
| + 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 memory to the untrusted plugin, so zero everything out first. |
|
bbudge
2012/03/26 22:52:30
Maybe memory -> data so it doesn't sound like a me
|
| + NaClMessageHeader header; |
| + memset(&header, 0, sizeof(NaClMessageHeader)); |
| + |
| + header.payload_size = 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 = 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 out buffer will point to the to_be_sent_ string which is |
|
bbudge
2012/03/26 22:52:30
s/out/our
|
| + // 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); |
| +} |