|  | // 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()); | 
|  | } |