// Copyright 2018 The Chromium 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 <cstdint> #include <string> #include "base/base_paths.h" #include "base/bind.h" #include "base/callback.h" #include "base/command_line.h" #include "base/files/file_path.h" #include "base/logging.h" #include "base/macros.h" #include "base/optional.h" #include "base/path_service.h" #include "base/run_loop.h" #include "base/synchronization/lock.h" #include "base/test/multiprocess_test.h" #include "base/test/scoped_task_environment.h" #include "base/threading/sequenced_task_runner_handle.h" #include "build/build_config.h" #include "mojo/core/test/mojo_test_base.h" #include "mojo/public/c/system/invitation.h" #include "mojo/public/cpp/platform/named_platform_channel.h" #include "mojo/public/cpp/platform/platform_channel.h" #include "mojo/public/cpp/system/platform_handle.h" namespace mojo { namespace core { namespace { enum class TransportType { kChannel, kChannelServer, }; const char kSecondaryChannelHandleSwitch[] = "test-secondary-channel-handle"; class InvitationTest : public test::MojoTestBase { public: InvitationTest() = default; ~InvitationTest() override = default; protected: static base::Process LaunchChildTestClient( const std::string& test_client_name, MojoHandle* primordial_pipes, size_t num_primordial_pipes, TransportType transport_type, MojoSendInvitationFlags send_flags, MojoProcessErrorHandler error_handler = nullptr, uintptr_t error_handler_context = 0, base::CommandLine* custom_command_line = nullptr, base::LaunchOptions* custom_launch_options = nullptr); static void SendInvitationToClient( PlatformHandle endpoint_handle, base::ProcessHandle process, MojoHandle* primordial_pipes, size_t num_primordial_pipes, TransportType transport_type, MojoSendInvitationFlags flags, MojoProcessErrorHandler error_handler, uintptr_t error_handler_context, base::StringPiece isolated_invitation_name); private: base::test::ScopedTaskEnvironment task_environment_; DISALLOW_COPY_AND_ASSIGN(InvitationTest); }; void PrepareToPassRemoteEndpoint(PlatformChannel* channel, base::LaunchOptions* options, base::CommandLine* command_line, base::StringPiece switch_name = {}) { std::string value; #if defined(OS_FUCHSIA) channel->PrepareToPassRemoteEndpoint(&options->handles_to_transfer, &value); #elif defined(OS_POSIX) channel->PrepareToPassRemoteEndpoint(&options->fds_to_remap, &value); #elif defined(OS_WIN) channel->PrepareToPassRemoteEndpoint(&options->handles_to_inherit, &value); #else #error "Platform not yet supported." #endif if (switch_name.empty()) switch_name = PlatformChannel::kHandleSwitch; command_line->AppendSwitchASCII(switch_name.as_string(), value); } TEST_F(InvitationTest, Create) { MojoHandle invitation; EXPECT_EQ(MOJO_RESULT_OK, MojoCreateInvitation(nullptr, &invitation)); EXPECT_EQ(MOJO_RESULT_OK, MojoClose(invitation)); MojoCreateInvitationOptions options; options.struct_size = sizeof(options); options.flags = MOJO_CREATE_INVITATION_FLAG_NONE; EXPECT_EQ(MOJO_RESULT_OK, MojoCreateInvitation(&options, &invitation)); EXPECT_EQ(MOJO_RESULT_OK, MojoClose(invitation)); } TEST_F(InvitationTest, InvalidArguments) { MojoHandle invitation; MojoCreateInvitationOptions invalid_create_options; invalid_create_options.struct_size = 0; EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoCreateInvitation(&invalid_create_options, &invitation)); EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoCreateInvitation(nullptr, nullptr)); // We need a valid invitation handle to exercise some of the other invalid // argument cases below. EXPECT_EQ(MOJO_RESULT_OK, MojoCreateInvitation(nullptr, &invitation)); MojoHandle pipe; MojoAttachMessagePipeToInvitationOptions invalid_attach_options; invalid_attach_options.struct_size = 0; EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoAttachMessagePipeToInvitation(MOJO_HANDLE_INVALID, "x", 1, nullptr, &pipe)); EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoAttachMessagePipeToInvitation(invitation, "x", 1, &invalid_attach_options, &pipe)); EXPECT_EQ( MOJO_RESULT_INVALID_ARGUMENT, MojoAttachMessagePipeToInvitation(invitation, "x", 1, nullptr, nullptr)); MojoExtractMessagePipeFromInvitationOptions invalid_extract_options; invalid_extract_options.struct_size = 0; EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoExtractMessagePipeFromInvitation(MOJO_HANDLE_INVALID, "x", 1, nullptr, &pipe)); EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoExtractMessagePipeFromInvitation( invitation, "x", 1, &invalid_extract_options, &pipe)); EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoExtractMessagePipeFromInvitation(invitation, "x", 1, nullptr, nullptr)); PlatformChannel channel; MojoPlatformHandle endpoint_handle; endpoint_handle.struct_size = sizeof(endpoint_handle); PlatformHandle::ToMojoPlatformHandle( channel.TakeLocalEndpoint().TakePlatformHandle(), &endpoint_handle); ASSERT_NE(endpoint_handle.type, MOJO_PLATFORM_HANDLE_TYPE_INVALID); MojoInvitationTransportEndpoint valid_endpoint; valid_endpoint.struct_size = sizeof(valid_endpoint); valid_endpoint.type = MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL; valid_endpoint.num_platform_handles = 1; valid_endpoint.platform_handles = &endpoint_handle; MojoSendInvitationOptions invalid_send_options; invalid_send_options.struct_size = 0; EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoSendInvitation(MOJO_HANDLE_INVALID, nullptr, &valid_endpoint, nullptr, 0, nullptr)); EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoSendInvitation(invitation, nullptr, &valid_endpoint, nullptr, 0, &invalid_send_options)); MojoInvitationTransportEndpoint invalid_endpoint; invalid_endpoint.struct_size = 0; invalid_endpoint.type = MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL; invalid_endpoint.num_platform_handles = 1; invalid_endpoint.platform_handles = &endpoint_handle; EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoSendInvitation(invitation, nullptr, &invalid_endpoint, nullptr, 0, nullptr)); invalid_endpoint.struct_size = sizeof(invalid_endpoint); invalid_endpoint.num_platform_handles = 0; EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoSendInvitation(invitation, nullptr, &invalid_endpoint, nullptr, 0, nullptr)); MojoPlatformHandle invalid_platform_handle; invalid_platform_handle.struct_size = 0; invalid_endpoint.num_platform_handles = 1; invalid_endpoint.platform_handles = &invalid_platform_handle; EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoSendInvitation(invitation, nullptr, &invalid_endpoint, nullptr, 0, nullptr)); invalid_platform_handle.struct_size = sizeof(invalid_platform_handle); invalid_platform_handle.type = MOJO_PLATFORM_HANDLE_TYPE_INVALID; EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoSendInvitation(invitation, nullptr, &invalid_endpoint, nullptr, 0, nullptr)); invalid_endpoint.num_platform_handles = 1; invalid_endpoint.platform_handles = nullptr; EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoSendInvitation(invitation, nullptr, &invalid_endpoint, nullptr, 0, nullptr)); MojoHandle accepted_invitation; MojoAcceptInvitationOptions invalid_accept_options; invalid_accept_options.struct_size = 0; EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoAcceptInvitation(nullptr, nullptr, &accepted_invitation)); EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoAcceptInvitation(&valid_endpoint, &invalid_accept_options, &accepted_invitation)); EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoAcceptInvitation(&valid_endpoint, nullptr, nullptr)); EXPECT_EQ(MOJO_RESULT_OK, MojoClose(invitation)); } TEST_F(InvitationTest, AttachAndExtractLocally) { MojoHandle invitation; EXPECT_EQ(MOJO_RESULT_OK, MojoCreateInvitation(nullptr, &invitation)); MojoHandle pipe0 = MOJO_HANDLE_INVALID; EXPECT_EQ(MOJO_RESULT_OK, MojoAttachMessagePipeToInvitation( invitation, "x", 1, nullptr, &pipe0)); EXPECT_NE(MOJO_HANDLE_INVALID, pipe0); MojoHandle pipe1 = MOJO_HANDLE_INVALID; EXPECT_EQ(MOJO_RESULT_OK, MojoExtractMessagePipeFromInvitation( invitation, "x", 1, nullptr, &pipe1)); EXPECT_NE(MOJO_HANDLE_INVALID, pipe1); EXPECT_EQ(MOJO_RESULT_OK, MojoClose(invitation)); // Should be able to communicate over the pipe. const std::string kMessage = "RSVP LOL"; WriteMessage(pipe0, kMessage); EXPECT_EQ(kMessage, ReadMessage(pipe1)); EXPECT_EQ(MOJO_RESULT_OK, MojoClose(pipe0)); EXPECT_EQ(MOJO_RESULT_OK, MojoClose(pipe1)); } TEST_F(InvitationTest, ClosedInvitationClosesAttachments) { MojoHandle invitation; EXPECT_EQ(MOJO_RESULT_OK, MojoCreateInvitation(nullptr, &invitation)); MojoHandle pipe = MOJO_HANDLE_INVALID; EXPECT_EQ(MOJO_RESULT_OK, MojoAttachMessagePipeToInvitation( invitation, "x", 1, nullptr, &pipe)); EXPECT_NE(MOJO_HANDLE_INVALID, pipe); // Closing the invitation should close |pipe|'s peer. EXPECT_EQ(MOJO_RESULT_OK, MojoClose(invitation)); EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(pipe, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); EXPECT_EQ(MOJO_RESULT_OK, MojoClose(pipe)); } TEST_F(InvitationTest, AttachNameInUse) { MojoHandle invitation; EXPECT_EQ(MOJO_RESULT_OK, MojoCreateInvitation(nullptr, &invitation)); MojoHandle pipe0 = MOJO_HANDLE_INVALID; EXPECT_EQ(MOJO_RESULT_OK, MojoAttachMessagePipeToInvitation( invitation, "x", 1, nullptr, &pipe0)); EXPECT_NE(MOJO_HANDLE_INVALID, pipe0); MojoHandle pipe1 = MOJO_HANDLE_INVALID; EXPECT_EQ( MOJO_RESULT_ALREADY_EXISTS, MojoAttachMessagePipeToInvitation(invitation, "x", 1, nullptr, &pipe1)); EXPECT_EQ(MOJO_HANDLE_INVALID, pipe1); EXPECT_EQ(MOJO_RESULT_OK, MojoAttachMessagePipeToInvitation( invitation, "y", 1, nullptr, &pipe1)); EXPECT_NE(MOJO_HANDLE_INVALID, pipe1); EXPECT_EQ(MOJO_RESULT_OK, MojoClose(invitation)); EXPECT_EQ(MOJO_RESULT_OK, MojoClose(pipe0)); EXPECT_EQ(MOJO_RESULT_OK, MojoClose(pipe1)); } // static base::Process InvitationTest::LaunchChildTestClient( const std::string& test_client_name, MojoHandle* primordial_pipes, size_t num_primordial_pipes, TransportType transport_type, MojoSendInvitationFlags send_flags, MojoProcessErrorHandler error_handler, uintptr_t error_handler_context, base::CommandLine* custom_command_line, base::LaunchOptions* custom_launch_options) { base::CommandLine default_command_line = base::GetMultiProcessTestChildBaseCommandLine(); base::CommandLine& command_line = custom_command_line ? *custom_command_line : default_command_line; base::LaunchOptions default_launch_options; base::LaunchOptions& launch_options = custom_launch_options ? *custom_launch_options : default_launch_options; #if defined(OS_WIN) launch_options.start_hidden = true; #endif base::Optional<PlatformChannel> channel; base::Optional<NamedPlatformChannel> named_channel; PlatformHandle local_endpoint_handle; if (transport_type == TransportType::kChannel) { channel.emplace(); PrepareToPassRemoteEndpoint(&channel.value(), &launch_options, &command_line); local_endpoint_handle = channel->TakeLocalEndpoint().TakePlatformHandle(); } else { #if defined(OS_FUCHSIA) NOTREACHED() << "Named pipe support does not exist for Mojo on Fuchsia."; #else NamedPlatformChannel::Options named_channel_options; #if !defined(OS_WIN) CHECK(base::PathService::Get(base::DIR_TEMP, &named_channel_options.socket_dir)); #endif named_channel.emplace(named_channel_options); named_channel->PassServerNameOnCommandLine(&command_line); local_endpoint_handle = named_channel->TakeServerEndpoint().TakePlatformHandle(); #endif } base::Process child_process = base::SpawnMultiProcessTestChild( test_client_name, command_line, launch_options); if (channel) channel->RemoteProcessLaunchAttempted(); SendInvitationToClient(std::move(local_endpoint_handle), child_process.Handle(), primordial_pipes, num_primordial_pipes, transport_type, send_flags, error_handler, error_handler_context, ""); return child_process; } // static void InvitationTest::SendInvitationToClient( PlatformHandle endpoint_handle, base::ProcessHandle process, MojoHandle* primordial_pipes, size_t num_primordial_pipes, TransportType transport_type, MojoSendInvitationFlags flags, MojoProcessErrorHandler error_handler, uintptr_t error_handler_context, base::StringPiece isolated_invitation_name) { MojoPlatformHandle handle; PlatformHandle::ToMojoPlatformHandle(std::move(endpoint_handle), &handle); CHECK_NE(handle.type, MOJO_PLATFORM_HANDLE_TYPE_INVALID); MojoHandle invitation; CHECK_EQ(MOJO_RESULT_OK, MojoCreateInvitation(nullptr, &invitation)); for (uint32_t name = 0; name < num_primordial_pipes; ++name) { CHECK_EQ(MOJO_RESULT_OK, MojoAttachMessagePipeToInvitation(invitation, &name, 4, nullptr, &primordial_pipes[name])); } MojoPlatformProcessHandle process_handle; process_handle.struct_size = sizeof(process_handle); #if defined(OS_WIN) process_handle.value = static_cast<uint64_t>(reinterpret_cast<uintptr_t>(process)); #else process_handle.value = static_cast<uint64_t>(process); #endif MojoInvitationTransportEndpoint transport_endpoint; transport_endpoint.struct_size = sizeof(transport_endpoint); if (transport_type == TransportType::kChannel) transport_endpoint.type = MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL; else transport_endpoint.type = MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL_SERVER; transport_endpoint.num_platform_handles = 1; transport_endpoint.platform_handles = &handle; MojoSendInvitationOptions options; options.struct_size = sizeof(options); options.flags = flags; if (flags & MOJO_SEND_INVITATION_FLAG_ISOLATED) { options.isolated_connection_name = isolated_invitation_name.data(); options.isolated_connection_name_length = static_cast<uint32_t>(isolated_invitation_name.size()); } CHECK_EQ(MOJO_RESULT_OK, MojoSendInvitation(invitation, &process_handle, &transport_endpoint, error_handler, error_handler_context, &options)); } class TestClientBase : public InvitationTest { public: static MojoHandle AcceptInvitation(MojoAcceptInvitationFlags flags, base::StringPiece switch_name = {}) { const auto& command_line = *base::CommandLine::ForCurrentProcess(); PlatformChannelEndpoint channel_endpoint = NamedPlatformChannel::ConnectToServer(command_line); if (!channel_endpoint.is_valid()) { if (switch_name.empty()) { channel_endpoint = PlatformChannel::RecoverPassedEndpointFromCommandLine(command_line); } else { channel_endpoint = PlatformChannel::RecoverPassedEndpointFromString( command_line.GetSwitchValueASCII(switch_name)); } } MojoPlatformHandle endpoint_handle; PlatformHandle::ToMojoPlatformHandle(channel_endpoint.TakePlatformHandle(), &endpoint_handle); CHECK_NE(endpoint_handle.type, MOJO_PLATFORM_HANDLE_TYPE_INVALID); MojoInvitationTransportEndpoint transport_endpoint; transport_endpoint.struct_size = sizeof(transport_endpoint); transport_endpoint.type = MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL; transport_endpoint.num_platform_handles = 1; transport_endpoint.platform_handles = &endpoint_handle; MojoAcceptInvitationOptions options; options.struct_size = sizeof(options); options.flags = flags; MojoHandle invitation; CHECK_EQ(MOJO_RESULT_OK, MojoAcceptInvitation(&transport_endpoint, &options, &invitation)); return invitation; } private: DISALLOW_COPY_AND_ASSIGN(TestClientBase); }; #define DEFINE_TEST_CLIENT(name) \ class name##Impl : public TestClientBase { \ public: \ static void Run(); \ }; \ MULTIPROCESS_TEST_MAIN(name) { \ name##Impl::Run(); \ return 0; \ } \ void name##Impl::Run() const std::string kTestMessage1 = "i am the pusher robot"; const std::string kTestMessage2 = "i push the messages down the pipe"; const std::string kTestMessage3 = "i am the shover robot"; const std::string kTestMessage4 = "i shove the messages down the pipe"; TEST_F(InvitationTest, SendInvitation) { MojoHandle primordial_pipe; base::Process child_process = LaunchChildTestClient( "SendInvitationClient", &primordial_pipe, 1, TransportType::kChannel, MOJO_SEND_INVITATION_FLAG_NONE); WriteMessage(primordial_pipe, kTestMessage1); EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_READABLE)); EXPECT_EQ(kTestMessage3, ReadMessage(primordial_pipe)); EXPECT_EQ(MOJO_RESULT_OK, MojoClose(primordial_pipe)); int wait_result = -1; base::WaitForMultiprocessTestChildExit( child_process, TestTimeouts::action_timeout(), &wait_result); child_process.Close(); EXPECT_EQ(0, wait_result); } DEFINE_TEST_CLIENT(SendInvitationClient) { MojoHandle primordial_pipe; MojoHandle invitation = AcceptInvitation(MOJO_ACCEPT_INVITATION_FLAG_NONE); const uint32_t pipe_name = 0; ASSERT_EQ(MOJO_RESULT_OK, MojoExtractMessagePipeFromInvitation(invitation, &pipe_name, 4, nullptr, &primordial_pipe)); ASSERT_EQ(MOJO_RESULT_OK, MojoClose(invitation)); WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_READABLE); ASSERT_EQ(kTestMessage1, ReadMessage(primordial_pipe)); WriteMessage(primordial_pipe, kTestMessage3); WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_PEER_CLOSED); ASSERT_EQ(MOJO_RESULT_OK, MojoClose(primordial_pipe)); } TEST_F(InvitationTest, SendInvitationMultiplePipes) { MojoHandle pipes[2]; base::Process child_process = LaunchChildTestClient( "SendInvitationMultiplePipesClient", pipes, 2, TransportType::kChannel, MOJO_SEND_INVITATION_FLAG_NONE); WriteMessage(pipes[0], kTestMessage1); WriteMessage(pipes[1], kTestMessage2); EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(pipes[0], MOJO_HANDLE_SIGNAL_READABLE)); EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(pipes[1], MOJO_HANDLE_SIGNAL_READABLE)); EXPECT_EQ(kTestMessage3, ReadMessage(pipes[0])); EXPECT_EQ(kTestMessage4, ReadMessage(pipes[1])); ASSERT_EQ(MOJO_RESULT_OK, MojoClose(pipes[0])); ASSERT_EQ(MOJO_RESULT_OK, MojoClose(pipes[1])); int wait_result = -1; base::WaitForMultiprocessTestChildExit( child_process, TestTimeouts::action_timeout(), &wait_result); child_process.Close(); EXPECT_EQ(0, wait_result); } DEFINE_TEST_CLIENT(SendInvitationMultiplePipesClient) { MojoHandle pipes[2]; MojoHandle invitation = AcceptInvitation(MOJO_ACCEPT_INVITATION_FLAG_NONE); const uint32_t pipe_names[] = {0, 1}; ASSERT_EQ(MOJO_RESULT_OK, MojoExtractMessagePipeFromInvitation(invitation, &pipe_names[0], 4, nullptr, &pipes[0])); ASSERT_EQ(MOJO_RESULT_OK, MojoExtractMessagePipeFromInvitation(invitation, &pipe_names[1], 4, nullptr, &pipes[1])); WaitForSignals(pipes[0], MOJO_HANDLE_SIGNAL_READABLE); WaitForSignals(pipes[1], MOJO_HANDLE_SIGNAL_READABLE); ASSERT_EQ(kTestMessage1, ReadMessage(pipes[0])); ASSERT_EQ(kTestMessage2, ReadMessage(pipes[1])); WriteMessage(pipes[0], kTestMessage3); WriteMessage(pipes[1], kTestMessage4); WaitForSignals(pipes[0], MOJO_HANDLE_SIGNAL_PEER_CLOSED); WaitForSignals(pipes[1], MOJO_HANDLE_SIGNAL_PEER_CLOSED); } #if !defined(OS_FUCHSIA) TEST_F(InvitationTest, SendInvitationWithServer) { MojoHandle primordial_pipe; base::Process child_process = LaunchChildTestClient( "SendInvitationWithServerClient", &primordial_pipe, 1, TransportType::kChannelServer, MOJO_SEND_INVITATION_FLAG_NONE); WriteMessage(primordial_pipe, kTestMessage1); EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_READABLE)); EXPECT_EQ(kTestMessage3, ReadMessage(primordial_pipe)); EXPECT_EQ(MOJO_RESULT_OK, MojoClose(primordial_pipe)); int wait_result = -1; base::WaitForMultiprocessTestChildExit( child_process, TestTimeouts::action_timeout(), &wait_result); child_process.Close(); EXPECT_EQ(0, wait_result); } DEFINE_TEST_CLIENT(SendInvitationWithServerClient) { MojoHandle primordial_pipe; MojoHandle invitation = AcceptInvitation(MOJO_ACCEPT_INVITATION_FLAG_NONE); const uint32_t pipe_name = 0; ASSERT_EQ(MOJO_RESULT_OK, MojoExtractMessagePipeFromInvitation(invitation, &pipe_name, 4, nullptr, &primordial_pipe)); ASSERT_EQ(MOJO_RESULT_OK, MojoClose(invitation)); WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_READABLE); ASSERT_EQ(kTestMessage1, ReadMessage(primordial_pipe)); WriteMessage(primordial_pipe, kTestMessage3); WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_PEER_CLOSED); ASSERT_EQ(MOJO_RESULT_OK, MojoClose(primordial_pipe)); } #endif // !defined(OS_FUCHSIA) const char kErrorMessage[] = "ur bad :("; const char kDisconnectMessage[] = "go away plz"; class RemoteProcessState { public: RemoteProcessState() : callback_task_runner_(base::SequencedTaskRunnerHandle::Get()) {} ~RemoteProcessState() = default; bool disconnected() { base::AutoLock lock(lock_); return disconnected_; } void set_error_callback(base::RepeatingClosure callback) { error_callback_ = std::move(callback); } void set_expected_error_message(const std::string& expected) { expected_error_message_ = expected; } void NotifyError(const std::string& error_message, bool disconnected) { base::AutoLock lock(lock_); CHECK(!disconnected_); EXPECT_NE(error_message.find(expected_error_message_), std::string::npos); disconnected_ = disconnected; ++call_count_; if (error_callback_) callback_task_runner_->PostTask(FROM_HERE, error_callback_); } private: const scoped_refptr<base::SequencedTaskRunner> callback_task_runner_; base::Lock lock_; int call_count_ = 0; bool disconnected_ = false; std::string expected_error_message_; base::RepeatingClosure error_callback_; DISALLOW_COPY_AND_ASSIGN(RemoteProcessState); }; void TestProcessErrorHandler(uintptr_t context, const MojoProcessErrorDetails* details) { auto* state = reinterpret_cast<RemoteProcessState*>(context); std::string error_message; if (details->error_message) { error_message = std::string(details->error_message, details->error_message_length - 1); } state->NotifyError(error_message, details->flags & MOJO_PROCESS_ERROR_FLAG_DISCONNECTED); } TEST_F(InvitationTest, ProcessErrors) { RemoteProcessState process_state; MojoHandle pipe; base::Process child_process = LaunchChildTestClient( "ProcessErrorsClient", &pipe, 1, TransportType::kChannel, MOJO_SEND_INVITATION_FLAG_NONE, &TestProcessErrorHandler, reinterpret_cast<uintptr_t>(&process_state)); MojoMessageHandle message; WaitForSignals(pipe, MOJO_HANDLE_SIGNAL_READABLE); EXPECT_EQ(MOJO_RESULT_OK, MojoReadMessage(pipe, nullptr, &message)); base::RunLoop error_loop; process_state.set_error_callback(error_loop.QuitClosure()); // Report this message as "bad". This should cause the error handler to be // invoked and the RunLoop to be quit. process_state.set_expected_error_message(kErrorMessage); EXPECT_EQ(MOJO_RESULT_OK, MojoNotifyBadMessage(message, kErrorMessage, sizeof(kErrorMessage), nullptr)); error_loop.Run(); EXPECT_EQ(MOJO_RESULT_OK, MojoDestroyMessage(message)); // Now tell the child it can exit, and wait for it to disconnect. base::RunLoop disconnect_loop; process_state.set_error_callback(disconnect_loop.QuitClosure()); process_state.set_expected_error_message(std::string()); WriteMessage(pipe, kDisconnectMessage); disconnect_loop.Run(); EXPECT_TRUE(process_state.disconnected()); int wait_result = -1; base::WaitForMultiprocessTestChildExit( child_process, TestTimeouts::action_timeout(), &wait_result); child_process.Close(); EXPECT_EQ(0, wait_result); } DEFINE_TEST_CLIENT(ProcessErrorsClient) { MojoHandle pipe; MojoHandle invitation = AcceptInvitation(MOJO_ACCEPT_INVITATION_FLAG_NONE); const uint32_t pipe_name = 0; ASSERT_EQ(MOJO_RESULT_OK, MojoExtractMessagePipeFromInvitation( invitation, &pipe_name, 4, nullptr, &pipe)); ASSERT_EQ(MOJO_RESULT_OK, MojoClose(invitation)); // Send a message. Contents are irrelevant, the test process is just going to // flag it as a bad. WriteMessage(pipe, "doesn't matter"); // Wait for our goodbye before exiting. WaitForSignals(pipe, MOJO_HANDLE_SIGNAL_READABLE); EXPECT_EQ(kDisconnectMessage, ReadMessage(pipe)); } TEST_F(InvitationTest, SendIsolatedInvitation) { MojoHandle primordial_pipe; base::Process child_process = LaunchChildTestClient( "SendIsolatedInvitationClient", &primordial_pipe, 1, TransportType::kChannel, MOJO_SEND_INVITATION_FLAG_ISOLATED); WriteMessage(primordial_pipe, kTestMessage1); EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_READABLE)); EXPECT_EQ(kTestMessage3, ReadMessage(primordial_pipe)); EXPECT_EQ(MOJO_RESULT_OK, MojoClose(primordial_pipe)); int wait_result = -1; base::WaitForMultiprocessTestChildExit( child_process, TestTimeouts::action_timeout(), &wait_result); child_process.Close(); EXPECT_EQ(0, wait_result); } DEFINE_TEST_CLIENT(SendIsolatedInvitationClient) { MojoHandle primordial_pipe; MojoHandle invitation = AcceptInvitation(MOJO_ACCEPT_INVITATION_FLAG_ISOLATED); const uint32_t pipe_name = 0; ASSERT_EQ(MOJO_RESULT_OK, MojoExtractMessagePipeFromInvitation(invitation, &pipe_name, 4, nullptr, &primordial_pipe)); ASSERT_EQ(MOJO_RESULT_OK, MojoClose(invitation)); WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_READABLE); ASSERT_EQ(kTestMessage1, ReadMessage(primordial_pipe)); WriteMessage(primordial_pipe, kTestMessage3); WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_PEER_CLOSED); ASSERT_EQ(MOJO_RESULT_OK, MojoClose(primordial_pipe)); } TEST_F(InvitationTest, SendMultipleIsolatedInvitations) { // We send a secondary transport to the client process so we can send a second // isolated invitation. base::CommandLine command_line = base::GetMultiProcessTestChildBaseCommandLine(); PlatformChannel secondary_transport; base::LaunchOptions options; PrepareToPassRemoteEndpoint(&secondary_transport, &options, &command_line, kSecondaryChannelHandleSwitch); MojoHandle primordial_pipe; base::Process child_process = LaunchChildTestClient( "SendMultipleIsolatedInvitationsClient", &primordial_pipe, 1, TransportType::kChannel, MOJO_SEND_INVITATION_FLAG_ISOLATED, nullptr, 0, &command_line, &options); secondary_transport.RemoteProcessLaunchAttempted(); WriteMessage(primordial_pipe, kTestMessage1); EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_READABLE)); EXPECT_EQ(kTestMessage3, ReadMessage(primordial_pipe)); // Send another invitation over our seconary pipe. This should trample the // original connection, breaking the first pipe. MojoHandle new_pipe; SendInvitationToClient( secondary_transport.TakeLocalEndpoint().TakePlatformHandle(), child_process.Handle(), &new_pipe, 1, TransportType::kChannel, MOJO_SEND_INVITATION_FLAG_ISOLATED, nullptr, 0, ""); WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_PEER_CLOSED); EXPECT_EQ(MOJO_RESULT_OK, MojoClose(primordial_pipe)); // And the new pipe should be working. WriteMessage(new_pipe, kTestMessage1); EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(new_pipe, MOJO_HANDLE_SIGNAL_READABLE)); EXPECT_EQ(kTestMessage3, ReadMessage(new_pipe)); EXPECT_EQ(MOJO_RESULT_OK, MojoClose(new_pipe)); int wait_result = -1; base::WaitForMultiprocessTestChildExit( child_process, TestTimeouts::action_timeout(), &wait_result); child_process.Close(); EXPECT_EQ(0, wait_result); } DEFINE_TEST_CLIENT(SendMultipleIsolatedInvitationsClient) { MojoHandle primordial_pipe; MojoHandle invitation = AcceptInvitation(MOJO_ACCEPT_INVITATION_FLAG_ISOLATED); const uint32_t pipe_name = 0; ASSERT_EQ(MOJO_RESULT_OK, MojoExtractMessagePipeFromInvitation(invitation, &pipe_name, 4, nullptr, &primordial_pipe)); ASSERT_EQ(MOJO_RESULT_OK, MojoClose(invitation)); WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_READABLE); ASSERT_EQ(kTestMessage1, ReadMessage(primordial_pipe)); WriteMessage(primordial_pipe, kTestMessage3); // The above pipe should get closed once we accept a new invitation. invitation = AcceptInvitation(MOJO_ACCEPT_INVITATION_FLAG_ISOLATED, kSecondaryChannelHandleSwitch); WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_PEER_CLOSED); primordial_pipe = MOJO_HANDLE_INVALID; ASSERT_EQ(MOJO_RESULT_OK, MojoExtractMessagePipeFromInvitation(invitation, &pipe_name, 4, nullptr, &primordial_pipe)); ASSERT_EQ(MOJO_RESULT_OK, MojoClose(invitation)); WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_READABLE); ASSERT_EQ(kTestMessage1, ReadMessage(primordial_pipe)); WriteMessage(primordial_pipe, kTestMessage3); WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_PEER_CLOSED); ASSERT_EQ(MOJO_RESULT_OK, MojoClose(primordial_pipe)); } TEST_F(InvitationTest, SendIsolatedInvitationWithDuplicateName) { PlatformChannel channel1; PlatformChannel channel2; MojoHandle pipe0, pipe1; const char kConnectionName[] = "there can be only one!"; SendInvitationToClient( channel1.TakeLocalEndpoint().TakePlatformHandle(), base::kNullProcessHandle, &pipe0, 1, TransportType::kChannel, MOJO_SEND_INVITATION_FLAG_ISOLATED, nullptr, 0, kConnectionName); // Send another invitation with the same connection name. |pipe0| should be // disconnected as the first invitation's connection is torn down. SendInvitationToClient( channel2.TakeLocalEndpoint().TakePlatformHandle(), base::kNullProcessHandle, &pipe1, 1, TransportType::kChannel, MOJO_SEND_INVITATION_FLAG_ISOLATED, nullptr, 0, kConnectionName); WaitForSignals(pipe0, MOJO_HANDLE_SIGNAL_PEER_CLOSED); } TEST_F(InvitationTest, SendIsolatedInvitationToSelf) { PlatformChannel channel; MojoHandle pipe0, pipe1; SendInvitationToClient(channel.TakeLocalEndpoint().TakePlatformHandle(), base::kNullProcessHandle, &pipe0, 1, TransportType::kChannel, MOJO_SEND_INVITATION_FLAG_ISOLATED, nullptr, 0, ""); SendInvitationToClient(channel.TakeRemoteEndpoint().TakePlatformHandle(), base::kNullProcessHandle, &pipe1, 1, TransportType::kChannel, MOJO_SEND_INVITATION_FLAG_ISOLATED, nullptr, 0, ""); WriteMessage(pipe0, kTestMessage1); EXPECT_EQ(kTestMessage1, ReadMessage(pipe1)); EXPECT_EQ(MOJO_RESULT_OK, MojoClose(pipe0)); EXPECT_EQ(MOJO_RESULT_OK, MojoClose(pipe1)); } TEST_F(InvitationTest, BrokenInvitationTransportBreaksAttachedPipe) { MojoHandle primordial_pipe; base::Process child_process = LaunchChildTestClient( "BrokenTransportClient", &primordial_pipe, 1, TransportType::kChannel, MOJO_SEND_INVITATION_FLAG_NONE); EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); EXPECT_EQ(MOJO_RESULT_OK, MojoClose(primordial_pipe)); int wait_result = -1; base::WaitForMultiprocessTestChildExit( child_process, TestTimeouts::action_timeout(), &wait_result); child_process.Close(); EXPECT_EQ(0, wait_result); } TEST_F(InvitationTest, BrokenIsolatedInvitationTransportBreaksAttachedPipe) { MojoHandle primordial_pipe; base::Process child_process = LaunchChildTestClient( "BrokenTransportClient", &primordial_pipe, 1, TransportType::kChannel, MOJO_SEND_INVITATION_FLAG_ISOLATED); EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); EXPECT_EQ(MOJO_RESULT_OK, MojoClose(primordial_pipe)); int wait_result = -1; base::WaitForMultiprocessTestChildExit( child_process, TestTimeouts::action_timeout(), &wait_result); child_process.Close(); EXPECT_EQ(0, wait_result); } DEFINE_TEST_CLIENT(BrokenTransportClient) { // No-op. Exit immediately without accepting any invitation. } } // namespace } // namespace core } // namespace mojo