// 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