blob: c15d862d7b739a6bad72a8cf70a94342a74454fe [file] [log] [blame]
// Copyright 2013 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 "mojo/system/message_in_transit.h"
#include <string.h>
#include <new>
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/memory/aligned_memory.h"
#include "mojo/system/constants.h"
namespace mojo {
namespace system {
STATIC_CONST_MEMBER_DEFINITION const MessageInTransit::Type
MessageInTransit::kTypeMessagePipeEndpoint;
STATIC_CONST_MEMBER_DEFINITION const MessageInTransit::Type
MessageInTransit::kTypeMessagePipe;
STATIC_CONST_MEMBER_DEFINITION const MessageInTransit::Type
MessageInTransit::kTypeChannel;
STATIC_CONST_MEMBER_DEFINITION const MessageInTransit::Subtype
MessageInTransit::kSubtypeMessagePipeEndpointData;
STATIC_CONST_MEMBER_DEFINITION const MessageInTransit::Subtype
MessageInTransit::kSubtypeChannelRunMessagePipeEndpoint;
STATIC_CONST_MEMBER_DEFINITION const MessageInTransit::Subtype
MessageInTransit::kSubtypeChannelRemoveMessagePipeEndpoint;
STATIC_CONST_MEMBER_DEFINITION const MessageInTransit::Subtype
MessageInTransit::kSubtypeChannelRemoveMessagePipeEndpointAck;
STATIC_CONST_MEMBER_DEFINITION const MessageInTransit::EndpointId
MessageInTransit::kInvalidEndpointId;
STATIC_CONST_MEMBER_DEFINITION const size_t MessageInTransit::kMessageAlignment;
STATIC_CONST_MEMBER_DEFINITION const size_t
MessageInTransit::kMaxSerializedDispatcherSize;
STATIC_CONST_MEMBER_DEFINITION const size_t
MessageInTransit::kMaxSerializedDispatcherPlatformHandles;
struct MessageInTransit::PrivateStructForCompileAsserts {
// The size of |Header| must be a multiple of the alignment.
COMPILE_ASSERT(sizeof(Header) % kMessageAlignment == 0,
sizeof_MessageInTransit_Header_invalid);
// Avoid dangerous situations, but making sure that the size of the "header" +
// the size of the data fits into a 31-bit number.
COMPILE_ASSERT(static_cast<uint64_t>(sizeof(Header)) + kMaxMessageNumBytes <=
0x7fffffffULL,
kMaxMessageNumBytes_too_big);
// We assume (to avoid extra rounding code) that the maximum message (data)
// size is a multiple of the alignment.
COMPILE_ASSERT(kMaxMessageNumBytes % kMessageAlignment == 0,
kMessageAlignment_not_a_multiple_of_alignment);
// The maximum serialized dispatcher size must be a multiple of the alignment.
COMPILE_ASSERT(kMaxSerializedDispatcherSize % kMessageAlignment == 0,
kMaxSerializedDispatcherSize_not_a_multiple_of_alignment);
// The size of |HandleTableEntry| must be a multiple of the alignment.
COMPILE_ASSERT(sizeof(HandleTableEntry) % kMessageAlignment == 0,
sizeof_MessageInTransit_HandleTableEntry_invalid);
};
// For each attached (Mojo) handle, there'll be a handle table entry and
// serialized dispatcher data.
// static
const size_t MessageInTransit::kMaxSecondaryBufferSize = kMaxMessageNumHandles *
(sizeof(HandleTableEntry) + kMaxSerializedDispatcherSize);
// static
const size_t MessageInTransit::kMaxPlatformHandles =
kMaxMessageNumHandles * kMaxSerializedDispatcherPlatformHandles;
MessageInTransit::View::View(size_t message_size, const void* buffer)
: buffer_(buffer) {
size_t next_message_size = 0;
DCHECK(MessageInTransit::GetNextMessageSize(buffer_, message_size,
&next_message_size));
DCHECK_EQ(message_size, next_message_size);
// This should be equivalent.
DCHECK_EQ(message_size, total_size());
}
bool MessageInTransit::View::IsValid(const char** error_message) const {
// Note: This also implies a check on the |main_buffer_size()|, which is just
// |RoundUpMessageAlignment(sizeof(Header) + num_bytes())|.
if (num_bytes() > kMaxMessageNumBytes) {
*error_message = "Message data payload too large";
return false;
}
if (const char* secondary_buffer_error_message =
ValidateSecondaryBuffer(num_handles(), secondary_buffer(),
secondary_buffer_size())) {
*error_message = secondary_buffer_error_message;
return false;
}
return true;
}
MessageInTransit::MessageInTransit(Type type,
Subtype subtype,
uint32_t num_bytes,
uint32_t num_handles,
const void* bytes)
: main_buffer_size_(RoundUpMessageAlignment(sizeof(Header) + num_bytes)),
main_buffer_(base::AlignedAlloc(main_buffer_size_, kMessageAlignment)),
secondary_buffer_size_(0),
secondary_buffer_(NULL) {
DCHECK_LE(num_bytes, kMaxMessageNumBytes);
DCHECK_LE(num_handles, kMaxMessageNumHandles);
// |total_size| is updated below, from the other values.
header()->type = type;
header()->subtype = subtype;
header()->source_id = kInvalidEndpointId;
header()->destination_id = kInvalidEndpointId;
header()->num_bytes = num_bytes;
header()->num_handles = num_handles;
// Note: If dispatchers are subsequently attached (in particular, if
// |num_handles| is nonzero), then |total_size| will have to be adjusted.
UpdateTotalSize();
if (bytes) {
memcpy(MessageInTransit::bytes(), bytes, num_bytes);
memset(static_cast<char*>(MessageInTransit::bytes()) + num_bytes, 0,
main_buffer_size_ - sizeof(Header) - num_bytes);
} else {
memset(MessageInTransit::bytes(), 0, main_buffer_size_ - sizeof(Header));
}
}
// TODO(vtl): Do I really want/need to copy the secondary buffer here, or should
// I just create (deserialize) the dispatchers right away?
MessageInTransit::MessageInTransit(const View& message_view)
: main_buffer_size_(message_view.main_buffer_size()),
main_buffer_(base::AlignedAlloc(main_buffer_size_, kMessageAlignment)),
secondary_buffer_size_(message_view.secondary_buffer_size()),
secondary_buffer_(secondary_buffer_size_ ?
base::AlignedAlloc(secondary_buffer_size_,
kMessageAlignment) : NULL) {
DCHECK_GE(main_buffer_size_, sizeof(Header));
DCHECK_EQ(main_buffer_size_ % kMessageAlignment, 0u);
memcpy(main_buffer_, message_view.main_buffer(), main_buffer_size_);
memcpy(secondary_buffer_, message_view.secondary_buffer(),
secondary_buffer_size_);
DCHECK_EQ(main_buffer_size_,
RoundUpMessageAlignment(sizeof(Header) + num_bytes()));
}
MessageInTransit::~MessageInTransit() {
base::AlignedFree(main_buffer_);
base::AlignedFree(secondary_buffer_); // Okay if null.
if (dispatchers_) {
for (size_t i = 0; i < dispatchers_->size(); i++) {
if (!(*dispatchers_)[i])
continue;
DCHECK((*dispatchers_)[i]->HasOneRef());
(*dispatchers_)[i]->Close();
}
}
if (platform_handles_) {
for (size_t i = 0; i < platform_handles_->size(); i++)
(*platform_handles_)[i].CloseIfNecessary();
}
#ifndef NDEBUG
main_buffer_size_ = 0;
main_buffer_ = NULL;
secondary_buffer_size_ = 0;
secondary_buffer_ = NULL;
dispatchers_.reset();
platform_handles_.reset();
#endif
}
// static
bool MessageInTransit::GetNextMessageSize(const void* buffer,
size_t buffer_size,
size_t* next_message_size) {
DCHECK(next_message_size);
if (!buffer_size)
return false;
DCHECK(buffer);
DCHECK_EQ(reinterpret_cast<uintptr_t>(buffer) %
MessageInTransit::kMessageAlignment, 0u);
if (buffer_size < sizeof(Header))
return false;
const Header* header = static_cast<const Header*>(buffer);
*next_message_size = header->total_size;
DCHECK_EQ(*next_message_size % kMessageAlignment, 0u);
return true;
}
void MessageInTransit::SetDispatchers(
scoped_ptr<std::vector<scoped_refptr<Dispatcher> > > dispatchers) {
DCHECK(dispatchers);
DCHECK(!dispatchers_);
dispatchers_ = dispatchers.Pass();
#ifndef NDEBUG
for (size_t i = 0; i < dispatchers_->size(); i++)
DCHECK(!(*dispatchers_)[i] || (*dispatchers_)[i]->HasOneRef());
#endif
}
void MessageInTransit::SerializeAndCloseDispatchers(Channel* channel) {
DCHECK(channel);
DCHECK(!secondary_buffer_);
CHECK_EQ(num_handles(),
dispatchers_ ? dispatchers_->size() : static_cast<size_t>(0));
if (!num_handles())
return;
// The offset to the start of the (Mojo) handle table.
// TODO(vtl): Add a header to the secondary buffer.
const size_t handle_table_start_offset = 0;
// The offset to the start of the serialized dispatcher data.
const size_t serialized_dispatcher_start_offset =
handle_table_start_offset + num_handles() * sizeof(HandleTableEntry);
// The size of the secondary buffer we'll add to this as we go along).
size_t size = serialized_dispatcher_start_offset;
size_t num_platform_handles = 0;
#if DCHECK_IS_ON
std::vector<size_t> all_max_sizes(num_handles());
std::vector<size_t> all_max_platform_handles(num_handles());
#endif
for (size_t i = 0; i < num_handles(); i++) {
if (Dispatcher* dispatcher = (*dispatchers_)[i].get()) {
size_t max_size = 0;
size_t max_platform_handles = 0;
Dispatcher::MessageInTransitAccess::StartSerialize(
dispatcher, channel, &max_size, &max_platform_handles);
DCHECK_LE(max_size, kMaxSerializedDispatcherSize);
size += RoundUpMessageAlignment(max_size);
DCHECK_LE(size, kMaxSecondaryBufferSize);
DCHECK_LE(max_platform_handles,
kMaxSerializedDispatcherPlatformHandles);
num_platform_handles += max_platform_handles;
DCHECK_LE(num_platform_handles, kMaxPlatformHandles);
#if DCHECK_IS_ON
all_max_sizes[i] = max_size;
all_max_platform_handles[i] = max_platform_handles;
#endif
}
}
secondary_buffer_ = base::AlignedAlloc(size, kMessageAlignment);
secondary_buffer_size_ = static_cast<uint32_t>(size);
// Entirely clear out the secondary buffer, since then we won't have to worry
// about clearing padding or unused space (e.g., if a dispatcher fails to
// serialize).
memset(secondary_buffer_, 0, size);
if (num_platform_handles > 0) {
DCHECK(!platform_handles_);
platform_handles_.reset(new std::vector<embedder::PlatformHandle>());
}
HandleTableEntry* handle_table = reinterpret_cast<HandleTableEntry*>(
static_cast<char*>(secondary_buffer_) + handle_table_start_offset);
size_t current_offset = serialized_dispatcher_start_offset;
for (size_t i = 0; i < num_handles(); i++) {
Dispatcher* dispatcher = (*dispatchers_)[i].get();
if (!dispatcher) {
COMPILE_ASSERT(Dispatcher::kTypeUnknown == 0,
value_of_Dispatcher_kTypeUnknown_must_be_zero);
continue;
}
#if DCHECK_IS_ON
size_t old_platform_handles_size =
platform_handles_ ? platform_handles_->size() : 0;
#endif
void* destination = static_cast<char*>(secondary_buffer_) + current_offset;
size_t actual_size = 0;
if (Dispatcher::MessageInTransitAccess::EndSerializeAndClose(
dispatcher, channel, destination, &actual_size,
platform_handles_.get())) {
handle_table[i].type = static_cast<int32_t>(dispatcher->GetType());
handle_table[i].offset = static_cast<uint32_t>(current_offset);
handle_table[i].size = static_cast<uint32_t>(actual_size);
#if DCHECK_IS_ON
DCHECK_LE(actual_size, all_max_sizes[i]);
DCHECK_LE(platform_handles_ ? (platform_handles_->size() -
old_platform_handles_size) : 0,
all_max_platform_handles[i]);
#endif
} else {
// Nothing to do on failure, since |secondary_buffer_| was cleared, and
// |kTypeUnknown| is zero. The handle was simply closed.
LOG(ERROR) << "Failed to serialize handle to remote message pipe";
}
current_offset += RoundUpMessageAlignment(actual_size);
DCHECK_LE(current_offset, size);
DCHECK_LE(platform_handles_ ? platform_handles_->size() : 0,
num_platform_handles);
}
UpdateTotalSize();
}
// Note: The message's secondary buffer should have been checked by calling
// |View::IsValid()| (on the |View|) first.
void MessageInTransit::DeserializeDispatchers(Channel* channel) {
DCHECK(!dispatchers_);
// Already checked by |View::IsValid()|:
DCHECK_LE(num_handles(), kMaxMessageNumHandles);
if (!num_handles())
return;
dispatchers_.reset(
new std::vector<scoped_refptr<Dispatcher> >(num_handles()));
size_t handle_table_size = num_handles() * sizeof(HandleTableEntry);
// Already checked by |View::IsValid()|:
DCHECK_LE(handle_table_size, secondary_buffer_size_);
const HandleTableEntry* handle_table =
static_cast<const HandleTableEntry*>(secondary_buffer_);
for (size_t i = 0; i < num_handles(); i++) {
size_t offset = handle_table[i].offset;
size_t size = handle_table[i].size;
// Already checked by |View::IsValid()|:
DCHECK_EQ(offset % kMessageAlignment, 0u);
DCHECK_LE(offset, secondary_buffer_size_);
DCHECK_LE(offset + size, secondary_buffer_size_);
const void* source = static_cast<const char*>(secondary_buffer_) + offset;
(*dispatchers_)[i] = Dispatcher::MessageInTransitAccess::Deserialize(
channel, handle_table[i].type, source, size);
}
}
// Validates the secondary buffer. Returns null on success, or a human-readable
// error message on error.
// static
const char* MessageInTransit::ValidateSecondaryBuffer(
size_t num_handles,
const void* secondary_buffer,
size_t secondary_buffer_size) {
// Always make sure that the secondary buffer size is sane (even if we have no
// handles); if it's not, someone's messing with us.
if (secondary_buffer_size > kMaxSecondaryBufferSize)
return "Message secondary buffer too large";
// Fast-path for the common case (no handles => no secondary buffer).
if (num_handles == 0) {
// We shouldn't have a secondary buffer in this case.
if (secondary_buffer_size > 0)
return "Message has no handles attached, but secondary buffer present";
return NULL;
}
// Sanity-check |num_handles| (before multiplying it against anything).
if (num_handles > kMaxMessageNumHandles)
return "Message handle payload too large";
if (secondary_buffer_size < num_handles * sizeof(HandleTableEntry))
return "Message secondary buffer too small";
DCHECK(secondary_buffer);
const HandleTableEntry* handle_table =
static_cast<const HandleTableEntry*>(secondary_buffer);
static const char kInvalidSerializedDispatcher[] =
"Message contains invalid serialized dispatcher";
for (size_t i = 0; i < num_handles; i++) {
size_t offset = handle_table[i].offset;
if (offset % kMessageAlignment != 0)
return kInvalidSerializedDispatcher;
size_t size = handle_table[i].size;
if (size > kMaxSerializedDispatcherSize || size > secondary_buffer_size)
return kInvalidSerializedDispatcher;
// Note: This is an overflow-safe check for |offset + size >
// secondary_buffer_size()| (we know that |size <= secondary_buffer_size()|
// from the previous check).
if (offset > secondary_buffer_size - size)
return kInvalidSerializedDispatcher;
}
return NULL;
}
void MessageInTransit::UpdateTotalSize() {
DCHECK_EQ(main_buffer_size_ % kMessageAlignment, 0u);
DCHECK_EQ(secondary_buffer_size_ % kMessageAlignment, 0u);
header()->total_size =
static_cast<uint32_t>(main_buffer_size_ + secondary_buffer_size_);
}
} // namespace system
} // namespace mojo