| Index: util/mach/mach_message_server_test.cc
|
| diff --git a/util/mach/mach_message_server_test.cc b/util/mach/mach_message_server_test.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..dcbbae388d7d0c30df44ee1905feed75e27b2cc1
|
| --- /dev/null
|
| +++ b/util/mach/mach_message_server_test.cc
|
| @@ -0,0 +1,517 @@
|
| +// Copyright 2014 The Crashpad Authors. All rights reserved.
|
| +//
|
| +// Licensed under the Apache License, Version 2.0 (the "License");
|
| +// you may not use this file except in compliance with the License.
|
| +// You may obtain a copy of the License at
|
| +//
|
| +// http://www.apache.org/licenses/LICENSE-2.0
|
| +//
|
| +// Unless required by applicable law or agreed to in writing, software
|
| +// distributed under the License is distributed on an "AS IS" BASIS,
|
| +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| +// See the License for the specific language governing permissions and
|
| +// limitations under the License.
|
| +
|
| +#include "util/mach/mach_message_server.h"
|
| +
|
| +#include <mach/mach.h>
|
| +#include <string.h>
|
| +
|
| +#include "base/basictypes.h"
|
| +#include "base/mac/scoped_mach_port.h"
|
| +#include "gtest/gtest.h"
|
| +#include "util/file/fd_io.h"
|
| +#include "util/test/errors.h"
|
| +#include "util/test/mac/mach_errors.h"
|
| +#include "util/test/mac/mach_multiprocess.h"
|
| +
|
| +namespace {
|
| +
|
| +using namespace crashpad;
|
| +using namespace crashpad::test;
|
| +
|
| +class TestMachMessageServer : public MachMessageServer::Interface,
|
| + public MachMultiprocess {
|
| + public:
|
| + struct Options {
|
| + // The type of reply port that the client should put in its request message.
|
| + enum ReplyPortType {
|
| + // The normal reply port is the client’s local port, to which it holds
|
| + // a receive right. This allows the server to respond directly to the
|
| + // client. The client will expect a reply.
|
| + kReplyPortNormal,
|
| +
|
| + // Use MACH_PORT_NULL as the reply port, which the server should detect
|
| + // avoid attempting to send a message to, and return success. The client
|
| + // will not expect a reply.
|
| + kReplyPortNull,
|
| +
|
| + // Make the server see the reply port as a dead name by setting the reply
|
| + // port to a receive right and then destroying that right before the
|
| + // server processes the request. The server should return
|
| + // MACH_SEND_INVALID_DEST, and the client will not expect a reply.
|
| + kReplyPortDead,
|
| + };
|
| +
|
| + Options()
|
| + : expect_server_interface_method_called(true),
|
| + parent_wait_for_child_pipe(false),
|
| + server_persistent(MachMessageServer::kOneShot),
|
| + server_nonblocking(MachMessageServer::kBlocking),
|
| + server_timeout_ms(MACH_MSG_TIMEOUT_NONE),
|
| + server_mig_retcode(KERN_SUCCESS),
|
| + expect_server_result(KERN_SUCCESS),
|
| + client_send_request_count(1),
|
| + client_reply_port_type(kReplyPortNormal),
|
| + child_send_all_requests_before_receiving_any_replies(false) {
|
| + }
|
| +
|
| + // true if MachMessageServerFunction() is expected to be called.
|
| + bool expect_server_interface_method_called;
|
| +
|
| + // true if the parent should wait for the child to write a byte to the pipe
|
| + // as a signal that the child is ready for the parent to begin its side of
|
| + // the test. This is used for nonblocking tests, which require that there
|
| + // be something in the server’s queue before attempting a nonblocking
|
| + // receive if the receive is to be successful.
|
| + bool parent_wait_for_child_pipe;
|
| +
|
| + // Whether the server should run in one-shot or persistent mode.
|
| + MachMessageServer::Persistent server_persistent;
|
| +
|
| + // Whether the server should run in blocking or nonblocking mode.
|
| + MachMessageServer::Nonblocking server_nonblocking;
|
| +
|
| + // The server’s timeout.
|
| + mach_msg_timeout_t server_timeout_ms;
|
| +
|
| + // The return code that the server returns to the client via the
|
| + // mig_reply_error_t::RetCode field. A client would normally see this as
|
| + // a Mach RPC return value.
|
| + kern_return_t server_mig_retcode;
|
| +
|
| + // The expected return value from MachMessageServer::Run().
|
| + kern_return_t expect_server_result;
|
| +
|
| + // The number of requests that the client should send to the server.
|
| + size_t client_send_request_count;
|
| +
|
| + // The type of reply port that the client should provide in its request’s
|
| + // mach_msg_header_t::msgh_local_port, which will appear to the server as
|
| + // mach_msg_header_t::msgh_remote_port.
|
| + ReplyPortType client_reply_port_type;
|
| +
|
| + // true if the client should send all requests before attempting to receive
|
| + // any replies from the server. This is used for the persistent nonblocking
|
| + // test, which requires the client to fill the server’s queue before the
|
| + // server can attempt processing it.
|
| + bool child_send_all_requests_before_receiving_any_replies;
|
| + };
|
| +
|
| + explicit TestMachMessageServer(const Options& options)
|
| + : MachMessageServer::Interface(),
|
| + MachMultiprocess(),
|
| + options_(options) {
|
| + }
|
| +
|
| + // Runs the test.
|
| + void Test() {
|
| + EXPECT_EQ(requests_, replies_);
|
| + uint32_t start = requests_;
|
| +
|
| + Run();
|
| +
|
| + EXPECT_EQ(requests_, replies_);
|
| + EXPECT_EQ(options_.client_send_request_count, requests_ - start);
|
| + }
|
| +
|
| + // MachMessageServerInterface:
|
| +
|
| + virtual bool MachMessageServerFunction(
|
| + mach_msg_header_t* in,
|
| + mach_msg_header_t* out,
|
| + bool* destroy_complex_request) override {
|
| + *destroy_complex_request = true;
|
| +
|
| + EXPECT_TRUE(options_.expect_server_interface_method_called);
|
| + if (!options_.expect_server_interface_method_called) {
|
| + return false;
|
| + }
|
| +
|
| + struct ReceiveRequestMessage : public RequestMessage {
|
| + mach_msg_trailer_t trailer;
|
| + };
|
| +
|
| + const ReceiveRequestMessage* request =
|
| + reinterpret_cast<ReceiveRequestMessage*>(in);
|
| + EXPECT_EQ(static_cast<mach_msg_bits_t>(
|
| + MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND, MACH_MSG_TYPE_MOVE_SEND)),
|
| + request->header.msgh_bits);
|
| + EXPECT_EQ(sizeof(RequestMessage), request->header.msgh_size);
|
| + if (options_.client_reply_port_type == Options::kReplyPortNormal) {
|
| + EXPECT_EQ(RemotePort(), request->header.msgh_remote_port);
|
| + }
|
| + EXPECT_EQ(LocalPort(), request->header.msgh_local_port);
|
| + EXPECT_EQ(kRequestMessageId, request->header.msgh_id);
|
| + EXPECT_EQ(0, memcmp(&request->ndr, &NDR_record, sizeof(NDR_record)));
|
| + EXPECT_EQ(requests_, request->number);
|
| + EXPECT_EQ(static_cast<mach_msg_trailer_type_t>(MACH_MSG_TRAILER_FORMAT_0),
|
| + request->trailer.msgh_trailer_type);
|
| + EXPECT_EQ(MACH_MSG_TRAILER_MINIMUM_SIZE,
|
| + request->trailer.msgh_trailer_size);
|
| +
|
| + ++requests_;
|
| +
|
| + ReplyMessage* reply = reinterpret_cast<ReplyMessage*>(out);
|
| + reply->Head.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
|
| + reply->Head.msgh_size = sizeof(*reply);
|
| + reply->Head.msgh_remote_port = request->header.msgh_remote_port;
|
| + reply->Head.msgh_local_port = MACH_PORT_NULL;
|
| + reply->Head.msgh_id = kReplyMessageId;
|
| + reply->NDR = NDR_record;
|
| + reply->RetCode = options_.server_mig_retcode;
|
| + reply->number = replies_++;
|
| +
|
| + return true;
|
| + }
|
| +
|
| + virtual mach_msg_size_t MachMessageServerRequestSize() override {
|
| + return sizeof(RequestMessage);
|
| + }
|
| +
|
| + virtual mach_msg_size_t MachMessageServerReplySize() override {
|
| + return sizeof(ReplyMessage);
|
| + }
|
| +
|
| + private:
|
| + struct RequestMessage {
|
| + mach_msg_header_t header;
|
| + NDR_record_t ndr;
|
| + uint32_t number;
|
| + };
|
| +
|
| + struct ReplyMessage : public mig_reply_error_t {
|
| + uint32_t number;
|
| + };
|
| +
|
| + // MachMultiprocess:
|
| +
|
| + virtual void MachMultiprocessParent() override {
|
| + if (options_.parent_wait_for_child_pipe) {
|
| + // Wait until the child is done sending what it’s going to send.
|
| + char c;
|
| + ssize_t rv = ReadFD(ReadPipeFD(), &c, 1);
|
| + EXPECT_EQ(1, rv) << ErrnoMessage("read");
|
| + EXPECT_EQ('\0', c);
|
| + }
|
| +
|
| + kern_return_t kr;
|
| + ASSERT_EQ(options_.expect_server_result,
|
| + (kr = MachMessageServer::Run(this,
|
| + LocalPort(),
|
| + MACH_MSG_OPTION_NONE,
|
| + options_.server_persistent,
|
| + options_.server_nonblocking,
|
| + options_.server_timeout_ms)))
|
| + << MachErrorMessage(kr, "MachMessageServer");
|
| + }
|
| +
|
| + virtual void MachMultiprocessChild() override {
|
| + for (size_t index = 0;
|
| + index < options_.client_send_request_count;
|
| + ++index) {
|
| + if (options_.child_send_all_requests_before_receiving_any_replies) {
|
| + // For this test, all of the messages need to go into the queue before
|
| + // the parent is allowed to start processing them. Don’t attempt to
|
| + // process replies before all of the requests are sent, because the
|
| + // server won’t have sent any replies until all of the requests are in
|
| + // its queue.
|
| + ChildSendRequest();
|
| + } else {
|
| + ChildSendRequestAndWaitForReply();
|
| + }
|
| + if (testing::Test::HasFatalFailure()) {
|
| + return;
|
| + }
|
| + }
|
| +
|
| + if (options_.parent_wait_for_child_pipe &&
|
| + options_.child_send_all_requests_before_receiving_any_replies) {
|
| + // Now that all of the requests have been sent, let the parent know that
|
| + // it’s safe to begin processing them, and then wait for the replies.
|
| + ChildNotifyParentViaPipe();
|
| + if (testing::Test::HasFatalFailure()) {
|
| + return;
|
| + }
|
| +
|
| + for (size_t index = 0;
|
| + index < options_.client_send_request_count;
|
| + ++index) {
|
| + ChildWaitForReply();
|
| + if (testing::Test::HasFatalFailure()) {
|
| + return;
|
| + }
|
| + }
|
| + }
|
| + }
|
| +
|
| + // In the child process, sends a request message to the server.
|
| + void ChildSendRequest() {
|
| + // local_receive_port_owner will the receive right that is created in this
|
| + // scope and intended to be destroyed when leaving this scope, after it has
|
| + // been carried in a Mach message.
|
| + base::mac::ScopedMachReceiveRight local_receive_port_owner;
|
| +
|
| + RequestMessage request = {};
|
| + request.header.msgh_bits =
|
| + MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND);
|
| + request.header.msgh_size = sizeof(request);
|
| + request.header.msgh_remote_port = RemotePort();
|
| + kern_return_t kr;
|
| + switch (options_.client_reply_port_type) {
|
| + case Options::kReplyPortNormal:
|
| + request.header.msgh_local_port = LocalPort();
|
| + break;
|
| + case Options::kReplyPortNull:
|
| + request.header.msgh_local_port = MACH_PORT_NULL;
|
| + break;
|
| + case Options::kReplyPortDead: {
|
| + // Use a newly-allocated receive right that will be destroyed when this
|
| + // method returns. A send right will be made from this receive right and
|
| + // carried in the request message to the server. By the time the server
|
| + // looks at the right, it will have become a dead name.
|
| + kr = mach_port_allocate(mach_task_self(),
|
| + MACH_PORT_RIGHT_RECEIVE,
|
| + &request.header.msgh_local_port);
|
| + ASSERT_EQ(KERN_SUCCESS, kr)
|
| + << MachErrorMessage(kr, "mach_port_allocate");
|
| + local_receive_port_owner.reset(request.header.msgh_local_port);
|
| + break;
|
| + }
|
| + }
|
| + request.header.msgh_id = kRequestMessageId;
|
| + request.number = requests_++;
|
| + request.ndr = NDR_record;
|
| +
|
| + kr = mach_msg(&request.header,
|
| + MACH_SEND_MSG | MACH_SEND_TIMEOUT,
|
| + request.header.msgh_size,
|
| + 0,
|
| + MACH_PORT_NULL,
|
| + MACH_MSG_TIMEOUT_NONE,
|
| + MACH_PORT_NULL);
|
| + ASSERT_EQ(MACH_MSG_SUCCESS, kr) << MachErrorMessage(kr, "mach_msg");
|
| + }
|
| +
|
| + // In the child process, waits for a reply message from the server.
|
| + void ChildWaitForReply() {
|
| + if (options_.client_reply_port_type != Options::kReplyPortNormal) {
|
| + // The client shouldn’t expect a reply when it didn’t send a good reply
|
| + // port with its request.
|
| + return;
|
| + }
|
| +
|
| + struct ReceiveReplyMessage : public ReplyMessage {
|
| + mach_msg_trailer_t trailer;
|
| + };
|
| +
|
| + ReceiveReplyMessage reply = {};
|
| + kern_return_t kr = mach_msg(&reply.Head,
|
| + MACH_RCV_MSG,
|
| + 0,
|
| + sizeof(reply),
|
| + LocalPort(),
|
| + MACH_MSG_TIMEOUT_NONE,
|
| + MACH_PORT_NULL);
|
| + ASSERT_EQ(MACH_MSG_SUCCESS, kr) << MachErrorMessage(kr, "mach_msg");
|
| +
|
| + ASSERT_EQ(static_cast<mach_msg_bits_t>(
|
| + MACH_MSGH_BITS(0, MACH_MSG_TYPE_MOVE_SEND)), reply.Head.msgh_bits);
|
| + ASSERT_EQ(sizeof(ReplyMessage), reply.Head.msgh_size);
|
| + ASSERT_EQ(static_cast<mach_port_t>(MACH_PORT_NULL),
|
| + reply.Head.msgh_remote_port);
|
| + ASSERT_EQ(LocalPort(), reply.Head.msgh_local_port);
|
| + ASSERT_EQ(kReplyMessageId, reply.Head.msgh_id);
|
| + ASSERT_EQ(0, memcmp(&reply.NDR, &NDR_record, sizeof(NDR_record)));
|
| + ASSERT_EQ(options_.server_mig_retcode, reply.RetCode);
|
| + ASSERT_EQ(replies_, reply.number);
|
| + ASSERT_EQ(static_cast<mach_msg_trailer_type_t>(MACH_MSG_TRAILER_FORMAT_0),
|
| + reply.trailer.msgh_trailer_type);
|
| + ASSERT_EQ(MACH_MSG_TRAILER_MINIMUM_SIZE, reply.trailer.msgh_trailer_size);
|
| +
|
| + ++replies_;
|
| + }
|
| +
|
| + // For test types where the child needs to notify the server in the parent
|
| + // that the child is ready, this method will send a byte via the POSIX pipe.
|
| + // The parent will be waiting in a read() on this pipe, and will proceed to
|
| + // running MachMessageServer() once it’s received.
|
| + void ChildNotifyParentViaPipe() {
|
| + char c = '\0';
|
| + ssize_t rv = WriteFD(WritePipeFD(), &c, 1);
|
| + ASSERT_EQ(1, rv) << ErrnoMessage("write");
|
| + }
|
| +
|
| + // In the child process, sends a request message to the server and then
|
| + // receives a reply message.
|
| + void ChildSendRequestAndWaitForReply() {
|
| + ChildSendRequest();
|
| + if (testing::Test::HasFatalFailure()) {
|
| + return;
|
| + }
|
| +
|
| + if (options_.parent_wait_for_child_pipe &&
|
| + !options_.child_send_all_requests_before_receiving_any_replies) {
|
| + // The parent is waiting to read a byte to indicate that the message has
|
| + // been placed in the queue.
|
| + ChildNotifyParentViaPipe();
|
| + if (testing::Test::HasFatalFailure()) {
|
| + return;
|
| + }
|
| + }
|
| +
|
| + ChildWaitForReply();
|
| + }
|
| +
|
| + const Options& options_;
|
| +
|
| + static uint32_t requests_;
|
| + static uint32_t replies_;
|
| +
|
| + static const mach_msg_id_t kRequestMessageId = 16237;
|
| + static const mach_msg_id_t kReplyMessageId = kRequestMessageId + 100;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(TestMachMessageServer);
|
| +};
|
| +
|
| +uint32_t TestMachMessageServer::requests_;
|
| +uint32_t TestMachMessageServer::replies_;
|
| +const mach_msg_id_t TestMachMessageServer::kRequestMessageId;
|
| +const mach_msg_id_t TestMachMessageServer::kReplyMessageId;
|
| +
|
| +TEST(MachMessageServer, Basic) {
|
| + // The client sends one message to the server, which will wait indefinitely in
|
| + // blocking mode for it.
|
| + TestMachMessageServer::Options options;
|
| + TestMachMessageServer test_mach_message_server(options);
|
| + test_mach_message_server.Test();
|
| +}
|
| +
|
| +TEST(MachMessageServer, NonblockingNoMessage) {
|
| + // The server waits in nonblocking mode and the client sends nothing, so the
|
| + // server should return immediately without processing any message.
|
| + TestMachMessageServer::Options options;
|
| + options.expect_server_interface_method_called = false;
|
| + options.server_nonblocking = MachMessageServer::kNonblocking;
|
| + options.expect_server_result = MACH_RCV_TIMED_OUT;
|
| + options.client_send_request_count = 0;
|
| + TestMachMessageServer test_mach_message_server(options);
|
| + test_mach_message_server.Test();
|
| +}
|
| +
|
| +TEST(MachMessageServer, TimeoutNoMessage) {
|
| + // The server waits in blocking mode for one message, but with a timeout. The
|
| + // client sends no message, so the server returns after the timeout.
|
| + TestMachMessageServer::Options options;
|
| + options.expect_server_interface_method_called = false;
|
| + options.server_timeout_ms = 10;
|
| + options.expect_server_result = MACH_RCV_TIMED_OUT;
|
| + options.client_send_request_count = 0;
|
| + TestMachMessageServer test_mach_message_server(options);
|
| + test_mach_message_server.Test();
|
| +}
|
| +
|
| +TEST(MachMessageServer, Nonblocking) {
|
| + // The client sends one message to the server and then signals the server that
|
| + // it’s safe to start waiting for it in nonblocking mode. The message is in
|
| + // the server’s queue, so it’s able to receive it when it begins listening in
|
| + // nonblocking mode.
|
| + TestMachMessageServer::Options options;
|
| + options.parent_wait_for_child_pipe = true;
|
| + options.server_nonblocking = MachMessageServer::kNonblocking;
|
| + TestMachMessageServer test_mach_message_server(options);
|
| + test_mach_message_server.Test();
|
| +}
|
| +
|
| +TEST(MachMessageServer, Timeout) {
|
| + // The client sends one message to the server, which will wait in blocking
|
| + // mode for it up to a specific timeout.
|
| + TestMachMessageServer::Options options;
|
| + options.server_timeout_ms = 10;
|
| + TestMachMessageServer test_mach_message_server(options);
|
| + test_mach_message_server.Test();
|
| +}
|
| +
|
| +TEST(MachMessageServer, PersistentTenMessages) {
|
| + // The server waits for as many messages as it can receive in blocking mode
|
| + // with a timeout. The client sends several messages, and the server processes
|
| + // them all.
|
| + TestMachMessageServer::Options options;
|
| + options.server_persistent = MachMessageServer::kPersistent;
|
| + options.server_timeout_ms = 10;
|
| + options.expect_server_result = MACH_RCV_TIMED_OUT;
|
| + options.client_send_request_count = 10;
|
| + TestMachMessageServer test_mach_message_server(options);
|
| + test_mach_message_server.Test();
|
| +}
|
| +
|
| +TEST(MachMessageServer, PersistentNonblockingFourMessages) {
|
| + // The client sends several messages to the server and then signals the server
|
| + // that it’s safe to start waiting for them in nonblocking mode. The server
|
| + // then listens for them in nonblocking persistent mode, and receives all of
|
| + // them because they’ve been queued up. The client doesn’t wait for the
|
| + // replies until after it’s put all of its requests into the server’s queue.
|
| + //
|
| + // This test is sensitive to the length of the IPC queue limit. Mach ports
|
| + // normally have a queue length limit of MACH_PORT_QLIMIT_DEFAULT (which is
|
| + // MACH_PORT_QLIMIT_BASIC, or 5). The number of messages sent for this test
|
| + // must be below this, because the server does not begin dequeueing request
|
| + // messages until the client has finished sending them.
|
| + TestMachMessageServer::Options options;
|
| + options.parent_wait_for_child_pipe = true;
|
| + options.server_persistent = MachMessageServer::kPersistent;
|
| + options.server_nonblocking = MachMessageServer::kNonblocking;
|
| + options.expect_server_result = MACH_RCV_TIMED_OUT;
|
| + options.client_send_request_count = 4;
|
| + options.child_send_all_requests_before_receiving_any_replies = true;
|
| + TestMachMessageServer test_mach_message_server(options);
|
| + test_mach_message_server.Test();
|
| +}
|
| +
|
| +TEST(MachMessageServer, ReturnCodeInvalidArgument) {
|
| + // This tests that the mig_reply_error_t::RetCode field is properly returned
|
| + // to the client.
|
| + TestMachMessageServer::Options options;
|
| + TestMachMessageServer test_mach_message_server(options);
|
| + options.server_mig_retcode = KERN_INVALID_ARGUMENT;
|
| + test_mach_message_server.Test();
|
| +}
|
| +
|
| +TEST(MachMessageServer, ReplyPortNull) {
|
| + // The client sets its reply port to MACH_PORT_NULL. The server should see
|
| + // this and avoid sending a message to the null port. No reply message is
|
| + // sent and the server returns success.
|
| + TestMachMessageServer::Options options;
|
| + TestMachMessageServer test_mach_message_server(options);
|
| + options.client_reply_port_type =
|
| + TestMachMessageServer::Options::kReplyPortNull;
|
| + test_mach_message_server.Test();
|
| +}
|
| +
|
| +TEST(MachMessageServer, ReplyPortDead) {
|
| + // The client allocates a new port and uses it as the reply port in its
|
| + // request message, and then deallocates its receive right to that port. It
|
| + // then signals the server to process the request message. The server’s view
|
| + // of the port is that it is a dead name. The server function will return
|
| + // MACH_SEND_INVALID_DEST because it’s not possible to send a message to a
|
| + // dead name.
|
| + TestMachMessageServer::Options options;
|
| + TestMachMessageServer test_mach_message_server(options);
|
| + options.parent_wait_for_child_pipe = true;
|
| + options.expect_server_result = MACH_SEND_INVALID_DEST;
|
| + options.client_reply_port_type =
|
| + TestMachMessageServer::Options::kReplyPortDead;
|
| + test_mach_message_server.Test();
|
| +}
|
| +
|
| +} // namespace
|
|
|