blob: 81b8decd1ef4d2153f575918533df4ea43702fd8 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include "object/message_packet.h"
#include <stdint.h>
#include <string.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <cstddef>
#include <cstdint>
#include <new>
#include <fbl/algorithm.h>
#include <ktl/algorithm.h>
#include <ktl/enforce.h>
// MessagePackets have special allocation requirements because they can contain a variable number of
// handles and a variable size payload.
//
// To reduce heap fragmentation, MessagePackets are stored in a lists of fixed size buffers
// (BufferChains) rather than a contiguous blocks of memory. These lists and buffers are allocated
// from the PMM.
//
// The first buffer in a MessagePacket's BufferChain contains the MessagePacket object, followed by
// its handles (if any), and finally its payload data (if any).
// The MessagePacket object, its handles and zx_txid_t must all fit in the first buffer.
static constexpr size_t kContiguousBytes =
sizeof(MessagePacket) + (kMaxMessageHandles * sizeof(Handle*)) + sizeof(zx_txid_t);
static_assert(kContiguousBytes <= BufferChain::kContig, "");
// Handles are stored just after the MessagePacket.
static constexpr uint32_t kHandlesOffset = static_cast<uint32_t>(sizeof(MessagePacket));
// PayloadOffset returns the offset of the data payload from the start of the first buffer.
static inline size_t PayloadOffset(size_t num_handles) {
// The payload comes after the handles.
return kHandlesOffset + num_handles * sizeof(Handle*);
}
// Creates a MessagePacket in |msg| sufficient to hold |data_size| bytes and |num_handles|.
//
// Note: This method does not write the payload into the MessagePacket.
//
// Returns ZX_OK on success.
//
// static
inline zx_status_t MessagePacket::CreateCommon(size_t data_size, size_t num_handles,
MessagePacketPtr* msg) {
if (unlikely(data_size > kMaxMessageSize || num_handles > kMaxMessageHandles)) {
return ZX_ERR_OUT_OF_RANGE;
}
const size_t payload_offset = PayloadOffset(num_handles);
// MessagePackets lives *inside* a list of buffers. The first buffer holds the MessagePacket
// object, followed by its handles (if any), and finally the payload data.
BufferChain* chain = BufferChain::Alloc(payload_offset + data_size);
if (unlikely(!chain)) {
return ZX_ERR_NO_MEMORY;
}
DEBUG_ASSERT(!chain->buffers()->is_empty());
chain->Skip(payload_offset);
char* const data = chain->buffers()->front().data();
Handle** const handles = reinterpret_cast<Handle**>(data + kHandlesOffset);
// Construct the MessagePacket into the first buffer.
MessagePacket* const packet = reinterpret_cast<MessagePacket*>(data);
static_assert(kMaxMessageHandles <= UINT16_MAX, "");
msg->reset(new (packet) MessagePacket(chain, static_cast<uint32_t>(data_size),
static_cast<uint32_t>(payload_offset),
static_cast<uint16_t>(num_handles), handles));
// The MessagePacket now owns the BufferChain and msg owns the MessagePacket.
return ZX_OK;
}
// static
zx_status_t MessagePacket::Create(user_in_ptr<const char> data, uint32_t data_size,
uint32_t num_handles, MessagePacketPtr* msg) {
MessagePacketPtr new_msg;
zx_status_t status = CreateCommon(data_size, num_handles, &new_msg);
if (unlikely(status != ZX_OK)) {
return status;
}
status = new_msg->buffer_chain_->Append(data, data_size);
if (unlikely(status != ZX_OK)) {
return status;
}
*msg = ktl::move(new_msg);
return ZX_OK;
}
// static
zx_status_t MessagePacket::Create(user_in_ptr<const zx_channel_iovec_t> iovecs, uint32_t num_iovecs,
uint32_t num_handles, MessagePacketPtr* msg) {
if (unlikely(num_iovecs > kMaxIovecsCount)) {
return ZX_ERR_OUT_OF_RANGE;
}
if (num_iovecs <= kIovecChunkSize) {
return CreateIovecBounded(iovecs, num_iovecs, num_handles, msg);
}
return CreateIovecUnbounded(iovecs, num_iovecs, num_handles, msg);
}
// static
zx_status_t MessagePacket::CreateIovecBounded(user_in_ptr<const zx_channel_iovec_t> user_iovecs,
uint32_t num_iovecs, uint32_t num_handles,
MessagePacketPtr* msg) {
DEBUG_ASSERT(num_iovecs <= kIovecChunkSize);
zx_channel_iovec_t iovecs[kIovecChunkSize];
if (num_iovecs > 0) {
zx_status_t status = user_iovecs.copy_array_from_user(iovecs, num_iovecs);
if (unlikely(status != ZX_OK)) {
return status;
}
}
size_t message_size = 0;
for (uint32_t i = 0; i < num_iovecs; i++) {
if (unlikely(iovecs[i].reserved != 0)) {
return ZX_ERR_INVALID_ARGS;
}
static_assert(sizeof(message_size) > sizeof(iovecs[i].capacity), "avoid overflow");
message_size += iovecs[i].capacity;
}
MessagePacketPtr new_msg;
zx_status_t status = CreateCommon(message_size, num_handles, &new_msg);
if (unlikely(status != ZX_OK)) {
return status;
}
for (uint32_t i = 0; i < num_iovecs; i++) {
user_in_ptr<const char> src(reinterpret_cast<const char*>(iovecs[i].buffer));
status = new_msg->buffer_chain_->Append(src, iovecs[i].capacity);
if (unlikely(status != ZX_OK)) {
return status;
}
}
*msg = ktl::move(new_msg);
return ZX_OK;
}
// static
zx_status_t MessagePacket::CreateIovecUnbounded(user_in_ptr<const zx_channel_iovec_t> user_iovecs,
uint32_t num_iovecs, uint32_t num_handles,
MessagePacketPtr* msg) {
MessagePacketPtr new_msg;
zx_status_t status = CreateCommon(kMaxMessageSize, num_handles, &new_msg);
if (unlikely(status != ZX_OK)) {
return status;
}
size_t message_size = 0;
while (num_iovecs > 0) {
uint32_t chunk_iovecs = ktl::min(num_iovecs, kIovecChunkSize);
zx_channel_iovec_t iovecs[kIovecChunkSize];
status = user_iovecs.copy_array_from_user(iovecs, chunk_iovecs);
if (unlikely(status != ZX_OK)) {
return status;
}
for (uint32_t i = 0; i < chunk_iovecs; i++) {
const zx_channel_iovec_t* iovec = &iovecs[i];
if (unlikely(iovec->reserved != 0)) {
return ZX_ERR_INVALID_ARGS;
}
static_assert(sizeof(message_size) > sizeof(iovec->capacity), "avoid overflow");
message_size += iovec->capacity;
user_in_ptr<const char> src(reinterpret_cast<const char*>(iovec->buffer));
status = new_msg->buffer_chain_->Append(src, iovec->capacity);
if (unlikely(status != ZX_OK)) {
return status;
}
}
num_iovecs -= chunk_iovecs;
user_iovecs = user_iovecs.element_offset(chunk_iovecs);
}
new_msg->buffer_chain_->FreeUnusedBuffers();
new_msg->set_data_size(static_cast<uint32_t>(message_size));
*msg = ktl::move(new_msg);
return ZX_OK;
}
// static
zx_status_t MessagePacket::Create(const char* data, uint32_t data_size, uint32_t num_handles,
MessagePacketPtr* msg) {
MessagePacketPtr new_msg;
zx_status_t status = CreateCommon(data_size, num_handles, &new_msg);
if (unlikely(status != ZX_OK)) {
return status;
}
status = new_msg->buffer_chain_->AppendKernel(data, data_size);
if (unlikely(status != ZX_OK)) {
return status;
}
*msg = ktl::move(new_msg);
return ZX_OK;
}
void MessagePacket::recycle(MessagePacket* packet) {
// Grab the buffer chain for this packet
BufferChain* chain = packet->buffer_chain_;
// Manually destruct the packet. Do not delete it; its memory did not come
// from new, it is contained as part of the buffer chain.
packet->~MessagePacket();
// Now return the buffer chain to where it came from.
BufferChain::Free(chain);
}