blob: bc7d84e1bf7283e2c4b5bd22a1175c4fd1940e21 [file] [log] [blame]
// Copyright 2020 The Fuchsia 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 <lib/zx/event.h>
#include <cstddef>
#include <cstdint>
#include <thread>
#include <zxtest/zxtest.h>
namespace {
// Wrap an array in a struct so it can be returned.
template <size_t N>
struct iovec_array_result {
zx_channel_iovec_t value[N];
};
template <size_t N, size_t M>
iovec_array_result<N> iovec_array(const char buffers[N][M]) {
iovec_array_result<N> result;
for (size_t i = 0; i < N; i++) {
result.value[i] = zx_channel_iovec_t{
.buffer = buffers[i],
.capacity = M,
.reserved = 0,
};
}
return result;
}
template <size_t NBufBytes = 65536, size_t NBufHandles = 64>
class EchoServer {
public:
EchoServer() {
zx::channel server_end;
ASSERT_OK(zx::channel::create(0, &client_end, &server_end));
thread = std::thread(ServerThread, std::move(server_end));
}
~EchoServer() { thread.join(); }
zx::channel ClientEnd() { return std::move(client_end); }
private:
static void ServerThread(zx::channel server_end) {
uint32_t actual_bytes;
uint32_t actual_handles;
uint8_t bytes[NBufBytes];
zx_handle_t handles[NBufHandles];
ASSERT_EQ(ZX_OK, server_end.wait_one(ZX_CHANNEL_READABLE, zx::time::infinite(), nullptr));
ASSERT_EQ(ZX_OK, server_end.read(0, bytes, handles, NBufBytes, NBufHandles, &actual_bytes,
&actual_handles));
ASSERT_EQ(ZX_OK, server_end.wait_one(ZX_CHANNEL_WRITABLE, zx::time::infinite(), nullptr));
ASSERT_EQ(ZX_OK, server_end.write(0, bytes, actual_bytes, handles, actual_handles));
}
zx::channel client_end;
std::thread thread;
};
TEST(IOVecTest, WriteZeroIovecs) {
zx::channel readCh, writeCh;
ASSERT_EQ(ZX_OK, zx::channel::create(0, &readCh, &writeCh));
ASSERT_EQ(ZX_OK, writeCh.write(ZX_CHANNEL_WRITE_USE_IOVEC, nullptr, 0, nullptr, 0));
uint32_t actual_bytes, actual_handles;
ASSERT_EQ(ZX_OK, readCh.read(0, nullptr, nullptr, 0, 0, &actual_bytes, &actual_handles));
EXPECT_EQ(0, actual_bytes);
EXPECT_EQ(0, actual_handles);
}
template <size_t NumIovecs, size_t BytesPerIovec>
void write_read_iovecs() {
zx::channel readCh, writeCh;
ASSERT_EQ(ZX_OK, zx::channel::create(0, &readCh, &writeCh));
char inputs[NumIovecs][BytesPerIovec];
for (size_t i = 0; i < NumIovecs; i++) {
// i/256 is added to the value because iovecs are stored in the kernel at
// 256 iovecs per page and all pages will look identically otherwise.
std::fill_n(inputs[i], sizeof(inputs[i]), static_cast<char>(i + (i / 256)));
}
auto iovecs = iovec_array<NumIovecs, BytesPerIovec>(inputs);
ASSERT_EQ(ZX_OK, writeCh.write(ZX_CHANNEL_WRITE_USE_IOVEC, iovecs.value, NumIovecs, nullptr, 0));
char output[65536];
uint32_t actual_bytes, actual_handles;
ASSERT_EQ(ZX_OK,
readCh.read(0, output, nullptr, sizeof(output), 0, &actual_bytes, &actual_handles));
ASSERT_EQ(NumIovecs * BytesPerIovec, actual_bytes);
ASSERT_EQ(0, actual_handles);
for (size_t i = 0; i < NumIovecs; i++) {
EXPECT_BYTES_EQ(inputs[i], &output[i * BytesPerIovec], BytesPerIovec);
}
}
// Test writing a small number of iovecs, which fit within the kernel stack buffer.
TEST(IOVecTest, WriteFewLargeIovecs) { write_read_iovecs<3, 8000>(); }
// Test writing a large number of iovecs, that don't fit within the stack buffer.
TEST(IOVecTest, WriteManySmallIovecs) { write_read_iovecs<1000, 10>(); }
TEST(IOVecTest, WriteWithHandle) {
zx::channel readCh, writeCh;
ASSERT_EQ(ZX_OK, zx::channel::create(0, &readCh, &writeCh));
char in_bytes[8] = {};
zx_channel_iovec_t in_iovecs[] = {
zx_channel_iovec_t{
.buffer = in_bytes,
.capacity = sizeof(in_bytes),
.reserved = 0,
},
};
zx::event in_handle;
ASSERT_EQ(ZX_OK, zx::event::create(0, &in_handle));
ASSERT_EQ(ZX_OK, writeCh.write(ZX_CHANNEL_WRITE_USE_IOVEC, in_iovecs, 1,
reinterpret_cast<zx_handle_t*>(&in_handle), 1));
char out_bytes[8];
zx::event out_handles[1] = {};
uint32_t actual_bytes, actual_handles;
ASSERT_EQ(ZX_OK,
readCh.read(0, out_bytes, reinterpret_cast<zx_handle_t*>(out_handles),
sizeof(out_bytes), sizeof(out_handles), &actual_bytes, &actual_handles));
ASSERT_EQ(8, actual_bytes);
EXPECT_BYTES_EQ(in_bytes, out_bytes, sizeof(in_bytes));
ASSERT_EQ(1, actual_handles);
ASSERT_TRUE(out_handles[0].is_valid());
}
TEST(IOVecTest, WriteEtcWithHandle) {
zx::channel readCh, writeCh;
ASSERT_EQ(ZX_OK, zx::channel::create(0, &readCh, &writeCh));
char in_bytes[8] = {};
zx_channel_iovec_t in_iovecs[] = {
zx_channel_iovec_t{
.buffer = in_bytes,
.capacity = sizeof(in_bytes),
.reserved = 0,
},
};
zx::event event;
ASSERT_EQ(ZX_OK, zx::event::create(0, &event));
zx_handle_disposition_t in_handle = {
.operation = ZX_HANDLE_OP_MOVE,
.handle = event.get(),
.type = ZX_OBJ_TYPE_EVENT,
.rights = ZX_RIGHT_SAME_RIGHTS,
.result = ZX_OK,
};
ASSERT_EQ(ZX_OK, writeCh.write_etc(ZX_CHANNEL_WRITE_USE_IOVEC, in_iovecs, 1, &in_handle, 1));
char out_bytes[8];
zx::event out_handles[1] = {};
uint32_t actual_bytes, actual_handles;
ASSERT_EQ(ZX_OK,
readCh.read(0, out_bytes, reinterpret_cast<zx_handle_t*>(out_handles),
sizeof(out_bytes), sizeof(out_handles), &actual_bytes, &actual_handles));
ASSERT_EQ(8, actual_bytes);
EXPECT_BYTES_EQ(in_bytes, out_bytes, sizeof(in_bytes));
ASSERT_EQ(1, actual_handles);
ASSERT_TRUE(out_handles[0].is_valid());
}
template <size_t NumIovecs, size_t BytesPerIovec>
void check_for_out_of_range_write() {
zx::channel readCh, writeCh;
ASSERT_EQ(ZX_OK, zx::channel::create(0, &readCh, &writeCh));
char inputs[NumIovecs][BytesPerIovec];
auto iovecs = iovec_array<NumIovecs, BytesPerIovec>(inputs);
ASSERT_EQ(ZX_ERR_OUT_OF_RANGE,
writeCh.write(ZX_CHANNEL_WRITE_USE_IOVEC, iovecs.value, NumIovecs, nullptr, 0));
}
TEST(IOVecTest, WriteTooManyIovecs) { check_for_out_of_range_write<9000, 1>(); }
TEST(IOVecTest, WriteTooManyBytes) { check_for_out_of_range_write<1000, 100>(); }
TEST(IOVecTest, WriteNonZeroReservedIovec) {
zx::channel readCh, writeCh;
ASSERT_EQ(ZX_OK, zx::channel::create(0, &readCh, &writeCh));
char buffer[256];
zx_channel_iovec_t iovecs[]{
zx_channel_iovec_t{
.buffer = buffer,
.capacity = sizeof(buffer),
.reserved = 1,
},
};
ASSERT_EQ(ZX_ERR_INVALID_ARGS, writeCh.write(ZX_CHANNEL_WRITE_USE_IOVEC, iovecs, 1, nullptr, 0));
}
TEST(IOVecTest, WriteNullBufferNonnullSize) {
zx::channel readCh, writeCh;
ASSERT_EQ(ZX_OK, zx::channel::create(0, &readCh, &writeCh));
zx_channel_iovec_t iovecs[]{
zx_channel_iovec_t{
.buffer = nullptr,
.capacity = 8,
.reserved = 0,
},
};
ASSERT_EQ(ZX_ERR_INVALID_ARGS, writeCh.write(ZX_CHANNEL_WRITE_USE_IOVEC, iovecs, 1, nullptr, 0));
}
TEST(IOVecTest, CallIovecBytesLessThanTxidSize) {
zx::channel clientCh, serverCh;
ASSERT_EQ(ZX_OK, zx::channel::create(0, &clientCh, &serverCh));
char inputs[3];
zx_channel_iovec_t iovecs[] = {
zx_channel_iovec_t{
.buffer = inputs,
.capacity = sizeof(inputs),
.reserved = 0,
},
};
zx_channel_call_args_t args = {
.wr_bytes = iovecs,
.wr_handles = nullptr,
.rd_bytes = nullptr,
.rd_handles = nullptr,
.wr_num_bytes = 1,
.wr_num_handles = 0,
.rd_num_bytes = 0,
.rd_num_handles = 0,
};
uint32_t actual_bytes, actual_handles;
ASSERT_EQ(ZX_ERR_INVALID_ARGS, clientCh.call(ZX_CHANNEL_WRITE_USE_IOVEC, zx::time::infinite(),
&args, &actual_bytes, &actual_handles));
}
template <size_t NumIovecs, size_t BytesPerIovec>
void call_iovecs() {
EchoServer echo_server;
zx::channel clientCh = echo_server.ClientEnd();
char inputs[NumIovecs][BytesPerIovec];
for (size_t i = 0; i < NumIovecs; i++) {
// i/256 is added to the value because iovecs are stored in the kernel at
// 256 iovecs per page and all pages will look identically otherwise.
std::fill_n(inputs[i], sizeof(inputs[i]), static_cast<char>(i + (i / 256)));
}
auto iovecs = iovec_array<NumIovecs, BytesPerIovec>(inputs);
char output[NumIovecs * BytesPerIovec];
zx_channel_call_args_t args{
.wr_bytes = iovecs.value,
.wr_handles = nullptr,
.rd_bytes = output,
.rd_handles = nullptr,
.wr_num_bytes = NumIovecs,
.wr_num_handles = 0,
.rd_num_bytes = sizeof(output),
.rd_num_handles = 0,
};
uint32_t actual_bytes, actual_handles;
ASSERT_EQ(ZX_OK, clientCh.call(ZX_CHANNEL_WRITE_USE_IOVEC, zx::time::infinite(), &args,
&actual_bytes, &actual_handles));
ASSERT_EQ(NumIovecs * BytesPerIovec, actual_bytes);
ASSERT_EQ(0, actual_handles);
char expected_output[NumIovecs * BytesPerIovec];
for (size_t i = 0; i < NumIovecs; i++) {
memcpy(&expected_output[i * BytesPerIovec], inputs[i], BytesPerIovec);
}
EXPECT_BYTES_EQ(expected_output + sizeof(zx_txid_t), output + +sizeof(zx_txid_t),
NumIovecs * BytesPerIovec - sizeof(zx_txid_t));
}
// Test writing a small number of iovecs, which fit within the kernel stack buffer.
TEST(IOVecTest, CallFewLargeIovecs) { call_iovecs<3, 8000>(); }
// Test writing a large number of iovecs, that don't fit within the stack buffer.
TEST(IOVecTest, CallManySmallIovecs) { call_iovecs<1000, 10>(); }
TEST(IOVecTest, CallWithHandle) {
EchoServer echo_server;
zx::channel clientCh = echo_server.ClientEnd();
char in_bytes[8] = {};
zx_channel_iovec_t in_iovecs[] = {
zx_channel_iovec_t{
.buffer = in_bytes,
.capacity = sizeof(in_bytes),
.reserved = 0,
},
};
zx::event in_handle;
ASSERT_EQ(ZX_OK, zx::event::create(0, &in_handle));
char out_bytes[8];
zx::event out_handles[1] = {};
zx_channel_call_args_t args{
.wr_bytes = in_iovecs,
.wr_handles = reinterpret_cast<zx_handle_t*>(&in_handle),
.rd_bytes = out_bytes,
.rd_handles = reinterpret_cast<zx_handle_t*>(out_handles),
.wr_num_bytes = 1,
.wr_num_handles = 1,
.rd_num_bytes = 8,
.rd_num_handles = 1,
};
uint32_t actual_bytes, actual_handles;
ASSERT_EQ(ZX_OK, clientCh.call(ZX_CHANNEL_WRITE_USE_IOVEC, zx::time::infinite(), &args,
&actual_bytes, &actual_handles));
ASSERT_EQ(8, actual_bytes);
EXPECT_BYTES_EQ(in_bytes + sizeof(zx_txid_t), out_bytes + sizeof(zx_txid_t),
sizeof(in_bytes) - sizeof(zx_txid_t));
ASSERT_EQ(1, actual_handles);
ASSERT_TRUE(out_handles[0].is_valid());
}
TEST(IOVecTest, CallEtcWithHandle) {
EchoServer echo_server;
zx::channel clientCh = echo_server.ClientEnd();
char in_bytes[8] = {};
zx_channel_iovec_t in_iovecs[] = {
zx_channel_iovec_t{
.buffer = in_bytes,
.capacity = sizeof(in_bytes),
.reserved = 0,
},
};
zx::event in_handle;
ASSERT_EQ(ZX_OK, zx::event::create(0, &in_handle));
zx_handle_disposition_t in_hds[] = {
zx_handle_disposition_t{
.operation = ZX_HANDLE_OP_MOVE,
.handle = in_handle.get(),
.type = ZX_OBJ_TYPE_EVENT,
.rights = ZX_RIGHT_SAME_RIGHTS,
.result = ZX_OK,
},
};
char out_bytes[8];
zx_handle_info_t out_his[1];
zx_channel_call_etc_args_t args{
.wr_bytes = in_iovecs,
.wr_handles = in_hds,
.rd_bytes = out_bytes,
.rd_handles = out_his,
.wr_num_bytes = 1,
.wr_num_handles = 1,
.rd_num_bytes = 8,
.rd_num_handles = 1,
};
uint32_t actual_bytes, actual_handles;
ASSERT_EQ(ZX_OK, clientCh.call_etc(ZX_CHANNEL_WRITE_USE_IOVEC, zx::time::infinite(), &args,
&actual_bytes, &actual_handles));
ASSERT_EQ(8, actual_bytes);
EXPECT_BYTES_EQ(in_bytes + sizeof(zx_txid_t), out_bytes + sizeof(zx_txid_t),
sizeof(in_bytes) - sizeof(zx_txid_t));
ASSERT_EQ(1, actual_handles);
ASSERT_NE(ZX_HANDLE_INVALID, out_his[0].handle);
ASSERT_EQ(ZX_OBJ_TYPE_EVENT, out_his[0].type);
zx_handle_close(out_his[0].handle);
}
template <size_t NumIovecs, size_t BytesPerIovec>
void check_for_out_of_range_call() {
zx::channel clientCh, serverCh;
ASSERT_EQ(ZX_OK, zx::channel::create(0, &clientCh, &serverCh));
char inputs[NumIovecs][BytesPerIovec];
auto iovecs = iovec_array<NumIovecs, BytesPerIovec>(inputs);
zx_channel_call_etc_args_t args{
.wr_bytes = iovecs.value,
.wr_handles = nullptr,
.rd_bytes = nullptr,
.rd_handles = nullptr,
.wr_num_bytes = NumIovecs,
.wr_num_handles = 0,
.rd_num_bytes = 0,
.rd_num_handles = 0,
};
uint32_t actual_bytes, actual_handles;
ASSERT_EQ(ZX_ERR_OUT_OF_RANGE, clientCh.call_etc(ZX_CHANNEL_WRITE_USE_IOVEC, zx::time::infinite(),
&args, &actual_bytes, &actual_handles));
}
TEST(IOVecTest, CallTooManyIovecs) { check_for_out_of_range_call<9000, 1>(); }
TEST(IOVecTest, CallTooManyBytes) { check_for_out_of_range_call<1000, 100>(); }
TEST(IOVecTest, CallNonZeroReservedIovec) {
zx::channel clientCh, serverCh;
ASSERT_EQ(ZX_OK, zx::channel::create(0, &clientCh, &serverCh));
char buffer[256];
zx_channel_iovec_t iovecs[]{
zx_channel_iovec_t{
.buffer = buffer,
.capacity = sizeof(buffer),
.reserved = 1,
},
};
zx_channel_call_args_t args{
.wr_bytes = iovecs,
.wr_handles = nullptr,
.rd_bytes = nullptr,
.rd_handles = nullptr,
.wr_num_bytes = 1,
.wr_num_handles = 0,
.rd_num_bytes = 0,
.rd_num_handles = 0,
};
uint32_t actual_bytes, actual_handles;
ASSERT_EQ(ZX_ERR_INVALID_ARGS, clientCh.call(ZX_CHANNEL_WRITE_USE_IOVEC, zx::time::infinite(),
&args, &actual_bytes, &actual_handles));
}
TEST(IOVecTest, CallNullBufferNonnullSize) {
zx::channel clientCh, serverCh;
ASSERT_EQ(ZX_OK, zx::channel::create(0, &clientCh, &serverCh));
zx_channel_iovec_t iovecs[]{
zx_channel_iovec_t{
.buffer = nullptr,
.capacity = 8,
.reserved = 0,
},
};
zx_channel_call_args_t args{
.wr_bytes = iovecs,
.wr_handles = nullptr,
.rd_bytes = nullptr,
.rd_handles = nullptr,
.wr_num_bytes = 1,
.wr_num_handles = 0,
.rd_num_bytes = 0,
.rd_num_handles = 0,
};
uint32_t actual_bytes, actual_handles;
ASSERT_EQ(ZX_ERR_INVALID_ARGS, clientCh.call(ZX_CHANNEL_WRITE_USE_IOVEC, zx::time::infinite(),
&args, &actual_bytes, &actual_handles));
}
} // namespace