blob: 17d8ecbc8923a8ef26e8c3a25e31d481ce4d0d44 [file] [log] [blame]
/*
* Copyright (C) 2010 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "Connection.h"
#include "CoreIPCMessageKinds.h"
#include "DataReference.h"
#include "MachPort.h"
#include "MachUtilities.h"
#include <WebCore/RunLoop.h>
#include <mach/mach_error.h>
#include <mach/vm_map.h>
#if HAVE(XPC)
#include <xpc/xpc.h>
#endif
using namespace std;
using namespace WebCore;
namespace CoreIPC {
static const size_t inlineMessageMaxSize = 4096;
enum {
MessageBodyIsOOL = 1 << 31
};
void Connection::platformInvalidate()
{
if (!m_isConnected)
return;
m_isConnected = false;
ASSERT(m_sendPort);
ASSERT(m_receivePort);
// Unregister our ports.
m_connectionQueue.unregisterMachPortEventHandler(m_sendPort);
m_sendPort = MACH_PORT_NULL;
m_connectionQueue.unregisterMachPortEventHandler(m_receivePort);
m_receivePort = MACH_PORT_NULL;
if (m_exceptionPort) {
m_connectionQueue.unregisterMachPortEventHandler(m_exceptionPort);
m_exceptionPort = MACH_PORT_NULL;
}
#if HAVE(XPC)
if (m_xpcConnection) {
xpc_release(m_xpcConnection);
m_xpcConnection = 0;
}
#endif
}
void Connection::platformInitialize(Identifier identifier)
{
m_exceptionPort = MACH_PORT_NULL;
if (m_isServer) {
m_receivePort = identifier.port;
m_sendPort = MACH_PORT_NULL;
} else {
m_receivePort = MACH_PORT_NULL;
m_sendPort = identifier.port;
}
#if HAVE(XPC)
m_xpcConnection = identifier.xpcConnection;
#endif
}
bool Connection::open()
{
if (m_isServer) {
ASSERT(m_receivePort);
ASSERT(!m_sendPort);
} else {
ASSERT(!m_receivePort);
ASSERT(m_sendPort);
// Create the receive port.
mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &m_receivePort);
m_isConnected = true;
// Send the initialize message, which contains a send right for the server to use.
OwnPtr<MessageEncoder> encoder = MessageEncoder::create("IPC", "", 0);
encoder->encode(MachPort(m_receivePort, MACH_MSG_TYPE_MAKE_SEND));
sendMessage(MessageID(CoreIPCMessage::InitializeConnection), encoder.release());
// Set the dead name handler for our send port.
initializeDeadNameSource();
}
// Change the message queue length for the receive port.
setMachPortQueueLength(m_receivePort, MACH_PORT_QLIMIT_LARGE);
// Register the data available handler.
m_connectionQueue.registerMachPortEventHandler(m_receivePort, WorkQueue::MachPortDataAvailable, bind(&Connection::receiveSourceEventHandler, this));
// If we have an exception port, register the data available handler and send over the port to the other end.
if (m_exceptionPort) {
m_connectionQueue.registerMachPortEventHandler(m_exceptionPort, WorkQueue::MachPortDataAvailable, bind(&Connection::exceptionSourceEventHandler, this));
OwnPtr<MessageEncoder> encoder = MessageEncoder::create("IPC", "", 0);
encoder->encode(MachPort(m_exceptionPort, MACH_MSG_TYPE_MAKE_SEND));
sendMessage(MessageID(CoreIPCMessage::SetExceptionPort), encoder.release());
}
return true;
}
static inline size_t machMessageSize(size_t bodySize, size_t numberOfPortDescriptors = 0, size_t numberOfOOLMemoryDescriptors = 0)
{
size_t size = sizeof(mach_msg_header_t) + bodySize;
if (numberOfPortDescriptors || numberOfOOLMemoryDescriptors) {
size += sizeof(mach_msg_body_t);
if (numberOfPortDescriptors)
size += (numberOfPortDescriptors * sizeof(mach_msg_port_descriptor_t));
if (numberOfOOLMemoryDescriptors)
size += (numberOfOOLMemoryDescriptors * sizeof(mach_msg_ool_ports_descriptor_t));
}
return round_msg(size);
}
bool Connection::platformCanSendOutgoingMessages() const
{
return true;
}
bool Connection::sendOutgoingMessage(MessageID messageID, PassOwnPtr<MessageEncoder> encoder)
{
Vector<Attachment> attachments = encoder->releaseAttachments();
size_t numberOfPortDescriptors = 0;
size_t numberOfOOLMemoryDescriptors = 0;
for (size_t i = 0; i < attachments.size(); ++i) {
Attachment::Type type = attachments[i].type();
if (type == Attachment::MachPortType)
numberOfPortDescriptors++;
else if (type == Attachment::MachOOLMemoryType)
numberOfOOLMemoryDescriptors++;
}
size_t messageSize = machMessageSize(encoder->bufferSize(), numberOfPortDescriptors, numberOfOOLMemoryDescriptors);
char buffer[inlineMessageMaxSize];
bool messageBodyIsOOL = false;
if (messageSize > sizeof(buffer)) {
messageBodyIsOOL = true;
attachments.append(Attachment(encoder->buffer(), encoder->bufferSize(), MACH_MSG_VIRTUAL_COPY, false));
numberOfOOLMemoryDescriptors++;
messageSize = machMessageSize(0, numberOfPortDescriptors, numberOfOOLMemoryDescriptors);
}
bool isComplex = (numberOfPortDescriptors + numberOfOOLMemoryDescriptors > 0);
mach_msg_header_t* header = reinterpret_cast<mach_msg_header_t*>(&buffer);
header->msgh_bits = isComplex ? MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND | MACH_MSGH_BITS_COMPLEX, 0) : MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
header->msgh_size = messageSize;
header->msgh_remote_port = m_sendPort;
header->msgh_local_port = MACH_PORT_NULL;
header->msgh_id = messageID.toInt();
if (messageBodyIsOOL)
header->msgh_id |= MessageBodyIsOOL;
uint8_t* messageData;
if (isComplex) {
mach_msg_body_t* body = reinterpret_cast<mach_msg_body_t*>(header + 1);
body->msgh_descriptor_count = numberOfPortDescriptors + numberOfOOLMemoryDescriptors;
uint8_t* descriptorData = reinterpret_cast<uint8_t*>(body + 1);
for (size_t i = 0; i < attachments.size(); ++i) {
Attachment attachment = attachments[i];
mach_msg_descriptor_t* descriptor = reinterpret_cast<mach_msg_descriptor_t*>(descriptorData);
switch (attachment.type()) {
case Attachment::MachPortType:
descriptor->port.name = attachment.port();
descriptor->port.disposition = attachment.disposition();
descriptor->port.type = MACH_MSG_PORT_DESCRIPTOR;
descriptorData += sizeof(mach_msg_port_descriptor_t);
break;
case Attachment::MachOOLMemoryType:
descriptor->out_of_line.address = attachment.address();
descriptor->out_of_line.size = attachment.size();
descriptor->out_of_line.copy = attachment.copyOptions();
descriptor->out_of_line.deallocate = attachment.deallocate();
descriptor->out_of_line.type = MACH_MSG_OOL_DESCRIPTOR;
descriptorData += sizeof(mach_msg_ool_descriptor_t);
break;
default:
ASSERT_NOT_REACHED();
}
}
messageData = descriptorData;
} else
messageData = (uint8_t*)(header + 1);
// Copy the data if it is not being sent out-of-line.
if (!messageBodyIsOOL)
memcpy(messageData, encoder->buffer(), encoder->bufferSize());
ASSERT(m_sendPort);
// Send the message.
kern_return_t kr = mach_msg(header, MACH_SEND_MSG, messageSize, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
if (kr != KERN_SUCCESS) {
// FIXME: What should we do here?
}
return true;
}
void Connection::initializeDeadNameSource()
{
m_connectionQueue.registerMachPortEventHandler(m_sendPort, WorkQueue::MachPortDeadNameNotification, bind(&Connection::connectionDidClose, this));
}
static PassOwnPtr<MessageDecoder> createMessageDecoder(mach_msg_header_t* header)
{
if (!(header->msgh_bits & MACH_MSGH_BITS_COMPLEX)) {
// We have a simple message.
uint8_t* body = reinterpret_cast<uint8_t*>(header + 1);
size_t bodySize = header->msgh_size - sizeof(mach_msg_header_t);
return MessageDecoder::create(DataReference(body, bodySize));
}
bool messageBodyIsOOL = header->msgh_id & MessageBodyIsOOL;
mach_msg_body_t* body = reinterpret_cast<mach_msg_body_t*>(header + 1);
mach_msg_size_t numDescriptors = body->msgh_descriptor_count;
ASSERT(numDescriptors);
// Build attachment list
Deque<Attachment> attachments;
uint8_t* descriptorData = reinterpret_cast<uint8_t*>(body + 1);
// If the message body was sent out-of-line, don't treat the last descriptor
// as an attachment, since it is really the message body.
if (messageBodyIsOOL)
--numDescriptors;
for (mach_msg_size_t i = 0; i < numDescriptors; ++i) {
mach_msg_descriptor_t* descriptor = reinterpret_cast<mach_msg_descriptor_t*>(descriptorData);
switch (descriptor->type.type) {
case MACH_MSG_PORT_DESCRIPTOR:
attachments.append(Attachment(descriptor->port.name, descriptor->port.disposition));
descriptorData += sizeof(mach_msg_port_descriptor_t);
break;
case MACH_MSG_OOL_DESCRIPTOR:
attachments.append(Attachment(descriptor->out_of_line.address, descriptor->out_of_line.size,
descriptor->out_of_line.copy, descriptor->out_of_line.deallocate));
descriptorData += sizeof(mach_msg_ool_descriptor_t);
break;
default:
ASSERT(false && "Unhandled descriptor type");
}
}
if (messageBodyIsOOL) {
mach_msg_descriptor_t* descriptor = reinterpret_cast<mach_msg_descriptor_t*>(descriptorData);
ASSERT(descriptor->type.type == MACH_MSG_OOL_DESCRIPTOR);
Attachment messageBodyAttachment(descriptor->out_of_line.address, descriptor->out_of_line.size,
descriptor->out_of_line.copy, descriptor->out_of_line.deallocate);
uint8_t* messageBody = static_cast<uint8_t*>(messageBodyAttachment.address());
size_t messageBodySize = messageBodyAttachment.size();
OwnPtr<MessageDecoder> decoder;
if (attachments.isEmpty())
decoder = MessageDecoder::create(DataReference(messageBody, messageBodySize));
else
decoder = MessageDecoder::create(DataReference(messageBody, messageBodySize), attachments);
vm_deallocate(mach_task_self(), reinterpret_cast<vm_address_t>(messageBodyAttachment.address()), messageBodyAttachment.size());
return decoder.release();
}
uint8_t* messageBody = descriptorData;
size_t messageBodySize = header->msgh_size - (descriptorData - reinterpret_cast<uint8_t*>(header));
return MessageDecoder::create(DataReference(messageBody, messageBodySize), attachments);
}
// The receive buffer size should always include the maximum trailer size.
static const size_t receiveBufferSize = inlineMessageMaxSize + MAX_TRAILER_SIZE;
typedef Vector<char, receiveBufferSize> ReceiveBuffer;
static mach_msg_header_t* readFromMachPort(mach_port_t machPort, ReceiveBuffer& buffer)
{
buffer.resize(receiveBufferSize);
mach_msg_header_t* header = reinterpret_cast<mach_msg_header_t*>(buffer.data());
kern_return_t kr = mach_msg(header, MACH_RCV_MSG | MACH_RCV_LARGE | MACH_RCV_TIMEOUT, 0, buffer.size(), machPort, 0, MACH_PORT_NULL);
if (kr == MACH_RCV_TIMED_OUT)
return 0;
if (kr == MACH_RCV_TOO_LARGE) {
// The message was too large, resize the buffer and try again.
buffer.resize(header->msgh_size + MAX_TRAILER_SIZE);
header = reinterpret_cast<mach_msg_header_t*>(buffer.data());
kr = mach_msg(header, MACH_RCV_MSG | MACH_RCV_LARGE | MACH_RCV_TIMEOUT, 0, buffer.size(), machPort, 0, MACH_PORT_NULL);
ASSERT(kr != MACH_RCV_TOO_LARGE);
}
if (kr != MACH_MSG_SUCCESS) {
ASSERT_NOT_REACHED();
return 0;
}
return header;
}
void Connection::receiveSourceEventHandler()
{
ReceiveBuffer buffer;
mach_msg_header_t* header = readFromMachPort(m_receivePort, buffer);
if (!header)
return;
MessageID messageID = MessageID::fromInt(header->msgh_id);
OwnPtr<MessageDecoder> decoder = createMessageDecoder(header);
ASSERT(decoder);
if (messageID == MessageID(CoreIPCMessage::InitializeConnection)) {
ASSERT(m_isServer);
ASSERT(!m_isConnected);
ASSERT(!m_sendPort);
MachPort port;
if (!decoder->decode(port)) {
// FIXME: Disconnect.
return;
}
m_sendPort = port.port();
// Set the dead name source if needed.
if (m_sendPort)
initializeDeadNameSource();
m_isConnected = true;
// Send any pending outgoing messages.
sendOutgoingMessages();
return;
}
if (messageID == MessageID(CoreIPCMessage::SetExceptionPort)) {
MachPort exceptionPort;
if (!decoder->decode(exceptionPort))
return;
setMachExceptionPort(exceptionPort.port());
return;
}
processIncomingMessage(messageID, decoder.release());
}
void Connection::exceptionSourceEventHandler()
{
ReceiveBuffer buffer;
mach_msg_header_t* header = readFromMachPort(m_exceptionPort, buffer);
if (!header)
return;
// We've read the exception message. Now send it on to the real exception port.
// The remote port should have a send once right.
ASSERT(MACH_MSGH_BITS_REMOTE(header->msgh_bits) == MACH_MSG_TYPE_MOVE_SEND_ONCE);
// Now get the real exception port.
mach_port_t exceptionPort = machExceptionPort();
// First, get the complex bit from the source message.
mach_msg_bits_t messageBits = header->msgh_bits & MACH_MSGH_BITS_COMPLEX;
messageBits |= MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MOVE_SEND_ONCE);
header->msgh_bits = messageBits;
header->msgh_local_port = header->msgh_remote_port;
header->msgh_remote_port = exceptionPort;
// Now send along the message.
kern_return_t kr = mach_msg(header, MACH_SEND_MSG, header->msgh_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
if (kr != KERN_SUCCESS) {
LOG_ERROR("Failed to send message to real exception port. %s (%x)", mach_error_string(kr), kr);
ASSERT_NOT_REACHED();
}
connectionDidClose();
}
void Connection::setShouldCloseConnectionOnMachExceptions()
{
ASSERT(m_exceptionPort == MACH_PORT_NULL);
if (mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &m_exceptionPort) != KERN_SUCCESS)
ASSERT_NOT_REACHED();
if (mach_port_insert_right(mach_task_self(), m_exceptionPort, m_exceptionPort, MACH_MSG_TYPE_MAKE_SEND) != KERN_SUCCESS)
ASSERT_NOT_REACHED();
}
} // namespace CoreIPC