blob: 68701b429d64004a5f8c5e8f877e9c7040beb6be [file] [log] [blame] [edit]
// Copyright 2019 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/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/async/wait.h>
#include <zircon/fidl.h>
#include <zircon/status.h>
#include <gtest/gtest.h>
#include <llcpptest/flexible/test/llcpp/fidl.h>
namespace test = ::llcpp::llcpptest::flexible::test;
// The only difference between StrictUnboundedXUnion and StrictBoundedXUnion is that
// StrictBoundedXUnion limits the vector payload length to 200 bytes. Therefore, by observing that
// sizeof(test::ReceiveStrictEnvelope::ResultOf::GetUnboundedXUnion) is less than 200, we can
// guarantee that the response storage is not inlined. Rather, it is allocated on the heap.
static_assert(sizeof(test::ReceiveStrictEnvelope::ResultOf::GetUnboundedXUnion) < 200,
"Result of GetUnboundedXUnion should be stored as a pointer to heap allocation");
// GetBoundedXUnion should be inlined, because it is smaller than 512, but bigger than 200, making
// the entire ResultOf object bigger than 200. The assertion triggers when the ResultOf object size
// falls below 200, at which point we know it is physically incapable of holding a GetBoundedXUnion
// inline, so probably used heap allocation. Here we are trying to test this without plumbing extra
// flags which themselves need to be tested.
static_assert(sizeof(test::ReceiveStrictEnvelope::ResultOf::GetBoundedXUnion) > 200,
"Result of GetBoundedXUnion should be inlined");
// Implement a special server that returns xunion/tables with unknown ordinals.
// This is impossible to do when using the bindings normally. Here we use a normal server to
// set a tag in the response xunion corresponding to the FIDL call, and intercept and rewrite
// the xunion to an unknown ordinal using a special fidl::Transaction implementation.
namespace {
class RewriteTransaction : public fidl::Transaction {
public:
std::unique_ptr<Transaction> TakeOwnership() override {
ZX_ASSERT_MSG(false, "Never called");
return {};
}
void Close(zx_status_t epitaph) override {
ZX_ASSERT_MSG(false, "Transaction::Close called with epitaph %d", epitaph);
}
zx_status_t Reply(fidl::OutgoingMessage* indicator_msg) override {
ZX_ASSERT(txid_ != 0);
ZX_ASSERT(indicator_msg->byte_actual() >=
sizeof(test::ReceiveFlexibleEnvelope::GetUnknownXUnionMoreHandlesResponse));
char real_msg_bytes[ZX_CHANNEL_MAX_MSG_BYTES] = {};
zx_handle_disposition_t real_msg_handles[ZX_CHANNEL_MAX_MSG_HANDLES] = {};
reinterpret_cast<fidl_message_header_t*>(&real_msg_bytes[0])->txid = txid_;
fidl_outgoing_msg_t real_msg = {
.bytes = &real_msg_bytes[0],
.handles = &real_msg_handles[0],
.num_bytes = 0u,
.num_handles = 0u,
};
// Determine if |indicator_msg| has a xunion or a table, by inspecting the first few bytes.
auto maybe_vector =
reinterpret_cast<fidl_vector_t*>(indicator_msg->bytes() + sizeof(fidl_message_header_t));
if ((maybe_vector->count == 1 || maybe_vector->count == 2) &&
reinterpret_cast<uintptr_t>(maybe_vector->data) == FIDL_ALLOC_PRESENT) {
// Table
// Manually craft the actual response which has an unknown ordinal
auto real_response =
reinterpret_cast<fidl_table_t*>(&real_msg_bytes[sizeof(fidl_message_header_t)]);
real_response->envelopes.data = reinterpret_cast<void*>(FIDL_ALLOC_PRESENT);
if (maybe_vector->count == 1) {
// The |want_more_than_30_bytes_at_ordinal_3| field was set.
// Create a message with more bytes than expected
constexpr uint32_t kUnknownBytes = 5000;
constexpr uint32_t kUnknownHandles = 0;
real_response->envelopes.count = 3;
const auto envelope_header_offset =
sizeof(fidl_message_header_t) + sizeof(fidl_table_t) + sizeof(fidl_envelope_t) * 2;
const auto envelope_payload_offset = envelope_header_offset + sizeof(fidl_envelope_t);
auto envelope = reinterpret_cast<fidl_envelope_t*>(&real_msg_bytes[envelope_header_offset]);
*envelope = fidl_envelope_t{
.num_bytes = kUnknownBytes,
.num_handles = kUnknownHandles,
.presence = FIDL_ALLOC_PRESENT,
};
real_msg.num_bytes = envelope_payload_offset + kUnknownBytes;
real_msg.num_handles = kUnknownHandles;
memset(&real_msg_bytes[envelope_payload_offset], 0xAA, kUnknownBytes);
} else {
// The |want_more_than_4_handles_at_ordinal_4| field was set.
// Create a message with more handles than expected
constexpr uint32_t kUnknownBytes = 16;
constexpr uint32_t kUnknownHandles = ZX_CHANNEL_MAX_MSG_HANDLES;
for (uint32_t i = 0; i < kUnknownHandles; i++) {
ZX_ASSERT(zx_event_create(0, &real_msg_handles[i].handle) == ZX_OK);
}
real_response->envelopes.count = 4;
const auto envelope_header_offset =
sizeof(fidl_message_header_t) + sizeof(fidl_table_t) + sizeof(fidl_envelope_t) * 3;
const auto envelope_payload_offset = envelope_header_offset + sizeof(fidl_envelope_t);
auto envelope = reinterpret_cast<fidl_envelope_t*>(&real_msg_bytes[envelope_header_offset]);
*envelope = fidl_envelope_t{
.num_bytes = kUnknownBytes,
.num_handles = kUnknownHandles,
.presence = FIDL_ALLOC_PRESENT,
};
real_msg.num_bytes = envelope_payload_offset + kUnknownBytes;
real_msg.num_handles = kUnknownHandles;
memset(&real_msg_bytes[envelope_payload_offset], 0xBB, kUnknownBytes);
}
} else {
// Manually craft the actual response which has an unknown ordinal
constexpr uint32_t kBadOrdinal = 0x8badf00d;
static_assert(kBadOrdinal !=
static_cast<uint32_t>(test::FlexibleXUnion::Tag::kWantMoreThan30Bytes));
static_assert(kBadOrdinal !=
static_cast<uint32_t>(test::FlexibleXUnion::Tag::kWantMoreThan4Handles));
auto real_response =
reinterpret_cast<fidl_xunion_t*>(&real_msg_bytes[sizeof(fidl_message_header_t)]);
real_response->tag = kBadOrdinal;
auto indicator_response =
reinterpret_cast<test::ReceiveFlexibleEnvelope::GetUnknownXUnionMoreHandlesResponse*>(
indicator_msg->bytes());
switch (indicator_response->xu.which()) {
case test::FlexibleXUnion::Tag::kWantMoreThan30Bytes: {
// Create a message with more bytes than expected
constexpr uint32_t kUnknownBytes = 5000;
constexpr uint32_t kUnknownHandles = 0;
real_response->envelope = fidl_envelope_t{
.num_bytes = kUnknownBytes,
.num_handles = kUnknownHandles,
.presence = FIDL_ALLOC_PRESENT,
};
real_msg.num_bytes =
sizeof(fidl_message_header_t) + sizeof(fidl_xunion_t) + kUnknownBytes;
real_msg.num_handles = kUnknownHandles;
memset(&real_msg_bytes[sizeof(fidl_message_header_t) + sizeof(fidl_xunion_t)], 0xAA,
kUnknownBytes);
break;
}
case test::FlexibleXUnion::Tag::kWantMoreThan4Handles: {
// Create a message with more handles than expected
constexpr uint32_t kUnknownBytes = 16;
constexpr uint32_t kUnknownHandles = ZX_CHANNEL_MAX_MSG_HANDLES;
for (uint32_t i = 0; i < kUnknownHandles; i++) {
ZX_ASSERT(zx_event_create(0, &real_msg_handles[i].handle) == ZX_OK);
}
real_response->envelope = fidl_envelope_t{
.num_bytes = kUnknownBytes,
.num_handles = kUnknownHandles,
.presence = FIDL_ALLOC_PRESENT,
};
real_msg.num_bytes =
sizeof(fidl_message_header_t) + sizeof(fidl_xunion_t) + kUnknownBytes;
real_msg.num_handles = kUnknownHandles;
memset(&real_msg_bytes[sizeof(fidl_message_header_t) + sizeof(fidl_xunion_t)], 0xBB,
kUnknownBytes);
break;
}
case test::FlexibleXUnion::Tag::kUnknown:
ZX_ASSERT_MSG(false, "Cannot reach here");
}
}
zx_status_t status = channel_->write_etc(0, real_msg.bytes, real_msg.num_bytes,
real_msg.handles, real_msg.num_handles);
ZX_ASSERT(status == ZX_OK);
return ZX_OK;
}
RewriteTransaction(zx_txid_t txid, zx::unowned_channel channel)
: txid_(txid), channel_(std::move(channel)) {}
private:
zx_txid_t txid_;
zx::unowned_channel channel_;
};
class Server : test::ReceiveFlexibleEnvelope::Interface, private async_wait_t {
public:
void GetUnknownXUnionMoreBytes(GetUnknownXUnionMoreBytesCompleter::Sync& completer) override {
test::FlexibleXUnion xunion;
fidl::aligned<fidl::Array<uint8_t, 30>> array = {};
xunion.set_want_more_than_30_bytes(fidl::unowned_ptr(&array));
completer.Reply(std::move(xunion));
}
void GetUnknownXUnionMoreHandles(GetUnknownXUnionMoreHandlesCompleter::Sync& completer) override {
test::FlexibleXUnion xunion;
fidl::Array<zx::handle, 4> array = {};
xunion.set_want_more_than_4_handles(fidl::unowned_ptr(&array));
completer.Reply(std::move(xunion));
}
void GetUnknownTableMoreBytes(GetUnknownTableMoreBytesCompleter::Sync& completer) override {
fidl::aligned<fidl::Array<uint8_t, 30>> array = {};
auto table_builder =
test::FlexibleTable::UnownedBuilder().set_want_more_than_30_bytes_at_ordinal_3(
fidl::unowned_ptr(&array));
completer.Reply(table_builder.build());
}
void GetUnknownTableMoreHandles(GetUnknownTableMoreHandlesCompleter::Sync& completer) override {
fidl::aligned<fidl::Array<zx::handle, 4>> array = {};
auto table_builder =
test::FlexibleTable::UnownedBuilder().set_want_more_than_4_handles_at_ordinal_4(
fidl::unowned_ptr(&array));
completer.Reply(table_builder.build());
}
Server(async_dispatcher_t* dispatcher, zx::channel channel)
: async_wait_t({
.state = ASYNC_STATE_INIT,
.handler = &MessageHandler,
.object = channel.release(),
.trigger = ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED,
.options = 0,
}),
dispatcher_(dispatcher) {
async_begin_wait(dispatcher_, this);
}
~Server() {
async_cancel_wait(dispatcher_, this);
zx_handle_close(async_wait_t::object);
}
void HandleMessage(async_dispatcher_t* dispatcher, zx_status_t status,
const zx_packet_signal_t* signal) {
if (status != ZX_OK) {
return;
}
if (signal->observed & ZX_CHANNEL_READABLE) {
for (uint64_t i = 0; i < signal->count; i++) {
fidl_incoming_msg_t msg = {
.bytes = &bytes_[0],
.handles = &handles_[0],
.num_bytes = 0u,
.num_handles = 0u,
};
status = zx_channel_read_etc(async_wait_t::object, 0, msg.bytes, msg.handles,
ZX_CHANNEL_MAX_MSG_BYTES, ZX_CHANNEL_MAX_MSG_HANDLES,
&msg.num_bytes, &msg.num_handles);
if (status != ZX_OK || msg.num_bytes < sizeof(fidl_message_header_t)) {
return;
}
auto hdr = reinterpret_cast<fidl_message_header_t*>(msg.bytes);
RewriteTransaction txn(hdr->txid, zx::unowned_channel(async_wait_t::object));
test::ReceiveFlexibleEnvelope::Dispatch(this, &msg, &txn);
}
// Will only get here if every single message was handled synchronously and successfully.
async_begin_wait(dispatcher_, this);
} else {
ZX_ASSERT(signal->observed & ZX_CHANNEL_PEER_CLOSED);
}
}
// Implement the function required by |async_wait_t|.
static void MessageHandler(async_dispatcher_t* dispatcher, async_wait_t* wait, zx_status_t status,
const zx_packet_signal_t* signal) {
static_cast<Server*>(wait)->HandleMessage(dispatcher, status, signal);
}
private:
async_dispatcher_t* dispatcher_;
std::unique_ptr<char[]> bytes_ = std::make_unique<char[]>(ZX_CHANNEL_MAX_MSG_BYTES);
std::unique_ptr<zx_handle_info_t[]> handles_ =
std::make_unique<zx_handle_info_t[]>(ZX_CHANNEL_MAX_MSG_HANDLES);
};
} // namespace
class FlexibleEnvelopeTest : public ::testing::Test {
protected:
virtual void SetUp() {
loop_ = std::make_unique<async::Loop>(&kAsyncLoopConfigAttachToCurrentThread);
ASSERT_EQ(loop_->StartThread("test_llcpp_flexible_envelope_server"), ZX_OK);
ASSERT_EQ(zx::channel::create(0, &client_end_, &server_end_), ZX_OK);
server_ = std::make_unique<Server>(loop_->dispatcher(), std::move(server_end_));
}
virtual void TearDown() {
loop_->Quit();
loop_->JoinThreads();
}
test::ReceiveFlexibleEnvelope::SyncClient TakeClient() {
EXPECT_TRUE(client_end_.is_valid());
return test::ReceiveFlexibleEnvelope::SyncClient(std::move(client_end_));
}
private:
std::unique_ptr<async::Loop> loop_;
std::unique_ptr<Server> server_;
zx::channel client_end_;
zx::channel server_end_;
};
static_assert(fidl::internal::ClampedMessageSize<
test::ReceiveFlexibleEnvelope::GetUnknownXUnionMoreBytesResponse,
fidl::MessageDirection::kReceiving>() == ZX_CHANNEL_MAX_MSG_BYTES,
"Cannot assume any limit on byte size apart from the channel limit");
TEST_F(FlexibleEnvelopeTest, ReceiveUnknownVariantWithMoreBytes) {
auto client = TakeClient();
auto result = client.GetUnknownXUnionMoreBytes();
EXPECT_TRUE(result.ok());
EXPECT_EQ(result.error(), nullptr) << result.error();
ASSERT_EQ(result.status(), ZX_OK) << zx_status_get_string(result.status());
ASSERT_EQ(result.value().xu.which(), test::FlexibleXUnion::Tag::kUnknown);
}
static_assert(fidl::internal::ClampedHandleCount<
test::ReceiveFlexibleEnvelope::GetUnknownXUnionMoreHandlesResponse,
fidl::MessageDirection::kReceiving>() == ZX_CHANNEL_MAX_MSG_HANDLES,
"Cannot assume any limit on handle count apart from the channel limit");
TEST_F(FlexibleEnvelopeTest, ReceiveUnknownVariantWithMoreHandles) {
auto client = TakeClient();
auto result = client.GetUnknownXUnionMoreHandles();
EXPECT_TRUE(result.ok());
EXPECT_EQ(result.error(), nullptr) << result.error();
ASSERT_EQ(result.status(), ZX_OK) << zx_status_get_string(result.status());
ASSERT_EQ(result.value().xu.which(), test::FlexibleXUnion::Tag::kUnknown);
}
static_assert(fidl::internal::ClampedMessageSize<
test::ReceiveFlexibleEnvelope::GetUnknownTableMoreBytesResponse,
fidl::MessageDirection::kReceiving>() == ZX_CHANNEL_MAX_MSG_BYTES,
"Cannot assume any limit on byte size apart from the channel limit");
TEST_F(FlexibleEnvelopeTest, ReceiveUnknownTableFieldWithMoreBytes) {
auto client = TakeClient();
auto result = client.GetUnknownTableMoreBytes();
EXPECT_TRUE(result.ok());
EXPECT_EQ(result.error(), nullptr) << result.error();
ASSERT_EQ(result.status(), ZX_OK) << zx_status_get_string(result.status());
EXPECT_FALSE(result.value().t.has_want_more_than_30_bytes_at_ordinal_3());
EXPECT_FALSE(result.value().t.has_want_more_than_4_handles_at_ordinal_4());
}
static_assert(fidl::internal::ClampedHandleCount<
test::ReceiveFlexibleEnvelope::GetUnknownTableMoreHandlesResponse,
fidl::MessageDirection::kReceiving>() == ZX_CHANNEL_MAX_MSG_HANDLES,
"Cannot assume any limit on handle count apart from the channel limit");
TEST_F(FlexibleEnvelopeTest, ReceiveUnknownTableFieldWithMoreHandles) {
auto client = TakeClient();
auto result = client.GetUnknownTableMoreHandles();
EXPECT_TRUE(result.ok());
EXPECT_EQ(result.error(), nullptr) << result.error();
ASSERT_EQ(result.status(), ZX_OK) << zx_status_get_string(result.status());
EXPECT_FALSE(result.value().t.has_want_more_than_30_bytes_at_ordinal_3());
EXPECT_FALSE(result.value().t.has_want_more_than_4_handles_at_ordinal_4());
}