mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-09 22:26:06 +00:00
Add MachMessageServer and its test.
MachMessageServer is much like mach_msg_server() and mach_msg_server_once(), but with a C++ interface and with a number of deficiencies corrected. TEST=util_test MachMessageServer.* R=rsesek@chromium.org Review URL: https://codereview.chromium.org/544393002
This commit is contained in:
parent
2cae118b60
commit
67082c93b9
282
util/mach/mach_message_server.cc
Normal file
282
util/mach/mach_message_server.cc
Normal file
@ -0,0 +1,282 @@
|
|||||||
|
// 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_time.h>
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
#include "base/mac/mach_logging.h"
|
||||||
|
#include "base/mac/scoped_mach_vm.h"
|
||||||
|
|
||||||
|
namespace crashpad {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
mach_timebase_info_data_t* TimebaseInternal() {
|
||||||
|
mach_timebase_info_data_t* timebase_info = new mach_timebase_info_data_t;
|
||||||
|
kern_return_t kr = mach_timebase_info(timebase_info);
|
||||||
|
MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_timebase_info";
|
||||||
|
return timebase_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
mach_timebase_info_data_t* Timebase() {
|
||||||
|
static mach_timebase_info_data_t* timebase_info = TimebaseInternal();
|
||||||
|
return timebase_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t NanosecondTicks() {
|
||||||
|
uint64_t absolute_time = mach_absolute_time();
|
||||||
|
mach_timebase_info_data_t* timebase_info = Timebase();
|
||||||
|
return absolute_time * timebase_info->numer / timebase_info->denom;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int kNanosecondsPerMillisecond = 1E6;
|
||||||
|
|
||||||
|
// TimerRunning determines whether |deadline| has passed. If |deadline| is in
|
||||||
|
// the future, |*remaining_ms| is set to the number of milliseconds remaining,
|
||||||
|
// which will always be a positive value, and this function returns true. If
|
||||||
|
// |deadline| is zero (indicating that no timer is in effect), |*remaining_ms|
|
||||||
|
// is set to zero and this function returns true. Otherwise, this function
|
||||||
|
// returns false. |deadline| is specified on the same time base as is returned
|
||||||
|
// by NanosecondTicks().
|
||||||
|
bool TimerRunning(uint64_t deadline, mach_msg_timeout_t* remaining_ms) {
|
||||||
|
if (!deadline) {
|
||||||
|
*remaining_ms = MACH_MSG_TIMEOUT_NONE;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t now = NanosecondTicks();
|
||||||
|
|
||||||
|
if (now >= deadline) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t remaining = deadline - now;
|
||||||
|
|
||||||
|
// Round to the nearest millisecond, taking care not to overflow.
|
||||||
|
const int kHalfMillisecondInNanoseconds = kNanosecondsPerMillisecond / 2;
|
||||||
|
mach_msg_timeout_t remaining_mach;
|
||||||
|
if (remaining <=
|
||||||
|
std::numeric_limits<uint64_t>::max() - kHalfMillisecondInNanoseconds) {
|
||||||
|
remaining_mach = (remaining + kHalfMillisecondInNanoseconds) /
|
||||||
|
kNanosecondsPerMillisecond;
|
||||||
|
} else {
|
||||||
|
remaining_mach = remaining / kNanosecondsPerMillisecond;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remaining_mach == MACH_MSG_TIMEOUT_NONE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*remaining_ms = remaining_mach;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// This implementation is based on 10.9.4
|
||||||
|
// xnu-2422.110.17/libsyscall/mach/mach_msg.c mach_msg_server_once(), but
|
||||||
|
// adapted to local style using scopers, replacing the server callback function
|
||||||
|
// and |max_size| parameter with a C++ interface, and with the addition of the
|
||||||
|
// the |persistent| parameter allowing this function to serve as a stand-in for
|
||||||
|
// mach_msg_server(), the |nonblocking| parameter to control blocking directly,
|
||||||
|
// and the |timeout_ms| parameter allowing this function to not block
|
||||||
|
// indefinitely.
|
||||||
|
//
|
||||||
|
// static
|
||||||
|
mach_msg_return_t MachMessageServer::Run(Interface* interface,
|
||||||
|
mach_port_t receive_port,
|
||||||
|
mach_msg_options_t options,
|
||||||
|
Persistent persistent,
|
||||||
|
Nonblocking nonblocking,
|
||||||
|
mach_msg_timeout_t timeout_ms) {
|
||||||
|
options &= ~(MACH_RCV_MSG | MACH_SEND_MSG);
|
||||||
|
|
||||||
|
mach_msg_options_t timeout_options = MACH_RCV_TIMEOUT | MACH_SEND_TIMEOUT |
|
||||||
|
MACH_RCV_INTERRUPT | MACH_SEND_INTERRUPT;
|
||||||
|
|
||||||
|
uint64_t deadline;
|
||||||
|
if (nonblocking == kNonblocking) {
|
||||||
|
options |= timeout_options;
|
||||||
|
deadline = 0;
|
||||||
|
} else if (timeout_ms != MACH_MSG_TIMEOUT_NONE) {
|
||||||
|
options |= timeout_options;
|
||||||
|
deadline = NanosecondTicks() +
|
||||||
|
static_cast<uint64_t>(timeout_ms) * kNanosecondsPerMillisecond;
|
||||||
|
} else {
|
||||||
|
options &= ~timeout_options;
|
||||||
|
deadline = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
mach_msg_size_t trailer_alloc = REQUESTED_TRAILER_SIZE(options);
|
||||||
|
mach_msg_size_t max_request_size = interface->MachMessageServerRequestSize();
|
||||||
|
mach_msg_size_t request_alloc = round_page(max_request_size + trailer_alloc);
|
||||||
|
mach_msg_size_t request_size = (options & MACH_RCV_LARGE)
|
||||||
|
? request_alloc
|
||||||
|
: max_request_size + trailer_alloc;
|
||||||
|
|
||||||
|
mach_msg_size_t max_reply_size = interface->MachMessageServerReplySize();
|
||||||
|
mach_msg_size_t reply_alloc = round_page(
|
||||||
|
(options & MACH_SEND_TRAILER) ? (max_reply_size + MAX_TRAILER_SIZE)
|
||||||
|
: max_reply_size);
|
||||||
|
|
||||||
|
mach_port_t self = mach_task_self();
|
||||||
|
|
||||||
|
kern_return_t kr;
|
||||||
|
|
||||||
|
do {
|
||||||
|
mach_msg_size_t this_request_alloc = request_alloc;
|
||||||
|
mach_msg_size_t this_request_size = request_size;
|
||||||
|
|
||||||
|
base::mac::ScopedMachVM request_scoper;
|
||||||
|
mach_msg_header_t* request_header = NULL;
|
||||||
|
|
||||||
|
while (!request_scoper.address()) {
|
||||||
|
vm_address_t request_addr;
|
||||||
|
kr = vm_allocate(self,
|
||||||
|
&request_addr,
|
||||||
|
this_request_alloc,
|
||||||
|
VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_MACH_MSG));
|
||||||
|
if (kr != KERN_SUCCESS) {
|
||||||
|
return kr;
|
||||||
|
}
|
||||||
|
base::mac::ScopedMachVM trial_request_scoper(request_addr,
|
||||||
|
this_request_alloc);
|
||||||
|
request_header = reinterpret_cast<mach_msg_header_t*>(request_addr);
|
||||||
|
|
||||||
|
do {
|
||||||
|
// If |options| contains MACH_RCV_INTERRUPT, retry mach_msg() in a loop
|
||||||
|
// when it returns MACH_RCV_INTERRUPTED to recompute |remaining_ms|
|
||||||
|
// rather than allowing mach_msg() to retry using the original timeout
|
||||||
|
// value. See 10.9.4 xnu-2422.110.17/libsyscall/mach/mach_msg.c
|
||||||
|
// mach_msg().
|
||||||
|
mach_msg_timeout_t remaining_ms;
|
||||||
|
if (!TimerRunning(deadline, &remaining_ms)) {
|
||||||
|
return MACH_RCV_TIMED_OUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
kr = mach_msg(request_header,
|
||||||
|
options | MACH_RCV_MSG,
|
||||||
|
0,
|
||||||
|
this_request_size,
|
||||||
|
receive_port,
|
||||||
|
remaining_ms,
|
||||||
|
MACH_PORT_NULL);
|
||||||
|
} while (kr == MACH_RCV_INTERRUPTED);
|
||||||
|
|
||||||
|
if (kr == MACH_MSG_SUCCESS) {
|
||||||
|
request_scoper.swap(trial_request_scoper);
|
||||||
|
} else if (kr == MACH_RCV_TOO_LARGE && options & MACH_RCV_LARGE) {
|
||||||
|
this_request_size =
|
||||||
|
round_page(request_header->msgh_size + trailer_alloc);
|
||||||
|
this_request_alloc = this_request_size;
|
||||||
|
} else {
|
||||||
|
return kr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vm_address_t reply_addr;
|
||||||
|
kr = vm_allocate(self,
|
||||||
|
&reply_addr,
|
||||||
|
reply_alloc,
|
||||||
|
VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_MACH_MSG));
|
||||||
|
if (kr != KERN_SUCCESS) {
|
||||||
|
return kr;
|
||||||
|
}
|
||||||
|
|
||||||
|
base::mac::ScopedMachVM reply_scoper(reply_addr, reply_alloc);
|
||||||
|
|
||||||
|
mach_msg_header_t* reply_header =
|
||||||
|
reinterpret_cast<mach_msg_header_t*>(reply_addr);
|
||||||
|
bool destroy_complex_request = false;
|
||||||
|
interface->MachMessageServerFunction(
|
||||||
|
request_header, reply_header, &destroy_complex_request);
|
||||||
|
|
||||||
|
if (!(reply_header->msgh_bits & MACH_MSGH_BITS_COMPLEX)) {
|
||||||
|
mig_reply_error_t* reply_mig =
|
||||||
|
reinterpret_cast<mig_reply_error_t*>(reply_header);
|
||||||
|
if (reply_mig->RetCode == MIG_NO_REPLY) {
|
||||||
|
reply_header->msgh_remote_port = MACH_PORT_NULL;
|
||||||
|
} else if (reply_mig->RetCode != KERN_SUCCESS &&
|
||||||
|
request_header->msgh_bits & MACH_MSGH_BITS_COMPLEX) {
|
||||||
|
destroy_complex_request = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (destroy_complex_request &&
|
||||||
|
request_header->msgh_bits & MACH_MSGH_BITS_COMPLEX) {
|
||||||
|
request_header->msgh_remote_port = MACH_PORT_NULL;
|
||||||
|
mach_msg_destroy(request_header);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reply_header->msgh_remote_port != MACH_PORT_NULL) {
|
||||||
|
// If the reply port right is a send-once right, the send won’t block even
|
||||||
|
// if the remote side isn’t waiting for a message. No timeout is used,
|
||||||
|
// which keeps the communication on the kernel’s fast path. If the reply
|
||||||
|
// port right is a send right, MACH_SEND_TIMEOUT is used to avoid blocking
|
||||||
|
// indefinitely. This duplicates the logic in 10.9.4
|
||||||
|
// xnu-2422.110.17/libsyscall/mach/mach_msg.c mach_msg_server_once().
|
||||||
|
mach_msg_option_t send_options =
|
||||||
|
options | MACH_SEND_MSG |
|
||||||
|
(MACH_MSGH_BITS_REMOTE(reply_header->msgh_bits) ==
|
||||||
|
MACH_MSG_TYPE_MOVE_SEND_ONCE
|
||||||
|
? 0
|
||||||
|
: MACH_SEND_TIMEOUT);
|
||||||
|
|
||||||
|
bool running;
|
||||||
|
do {
|
||||||
|
// If |options| contains MACH_SEND_INTERRUPT, retry mach_msg() in a loop
|
||||||
|
// when it returns MACH_SEND_INTERRUPTED to recompute |remaining_ms|
|
||||||
|
// rather than allowing mach_msg() to retry using the original timeout
|
||||||
|
// value. See 10.9.4 xnu-2422.110.17/libsyscall/mach/mach_msg.c
|
||||||
|
// mach_msg().
|
||||||
|
mach_msg_timeout_t remaining_ms;
|
||||||
|
running = TimerRunning(deadline, &remaining_ms);
|
||||||
|
if (!running) {
|
||||||
|
// Don’t return just yet. If the timer ran out in between the time the
|
||||||
|
// request was received and now, at least try to send the response.
|
||||||
|
remaining_ms = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
kr = mach_msg(reply_header,
|
||||||
|
send_options,
|
||||||
|
reply_header->msgh_size,
|
||||||
|
0,
|
||||||
|
MACH_PORT_NULL,
|
||||||
|
remaining_ms,
|
||||||
|
MACH_PORT_NULL);
|
||||||
|
} while (kr == MACH_SEND_INTERRUPTED);
|
||||||
|
|
||||||
|
if (kr != KERN_SUCCESS) {
|
||||||
|
if (kr == MACH_SEND_INVALID_DEST || kr == MACH_SEND_TIMED_OUT) {
|
||||||
|
mach_msg_destroy(reply_header);
|
||||||
|
}
|
||||||
|
return kr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!running) {
|
||||||
|
// The reply message was sent successfuly, so act as though the deadline
|
||||||
|
// was reached before or during the subsequent receive operation when in
|
||||||
|
// persistent mode, and just return success when not in persistent mode.
|
||||||
|
return (persistent == kPersistent) ? MACH_RCV_TIMED_OUT : kr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (persistent == kPersistent);
|
||||||
|
|
||||||
|
return kr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace crashpad
|
154
util/mach/mach_message_server.h
Normal file
154
util/mach/mach_message_server.h
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
#ifndef CRASHPAD_UTIL_MACH_MACH_MESSAGE_SERVER_H_
|
||||||
|
#define CRASHPAD_UTIL_MACH_MACH_MESSAGE_SERVER_H_
|
||||||
|
|
||||||
|
#include <mach/mach.h>
|
||||||
|
|
||||||
|
#include "base/basictypes.h"
|
||||||
|
|
||||||
|
namespace crashpad {
|
||||||
|
|
||||||
|
//! \brief Runs a Mach message server to handle a Mach RPC request for MIG
|
||||||
|
//! servers.
|
||||||
|
//!
|
||||||
|
//! The principal entry point to this interface is the static Run() method.
|
||||||
|
class MachMessageServer {
|
||||||
|
public:
|
||||||
|
//! \brief A Mach RPC callback interface, called by Run().
|
||||||
|
class Interface {
|
||||||
|
public:
|
||||||
|
//! \brief Handles a Mach RPC request.
|
||||||
|
//!
|
||||||
|
//! This method is a stand-in for a MIG-generated Mach RPC server “demux”
|
||||||
|
//! function such as `exc_server()` and `mach_exc_server()`. Implementations
|
||||||
|
//! may call such a function directly. This method is expected to behave
|
||||||
|
//! exactly as these functions behave.
|
||||||
|
//!
|
||||||
|
//! \param[in] in The request message, received as a Mach message.
|
||||||
|
//! \param[out] out The reply message. The caller allocates storage, and the
|
||||||
|
//! callee is expected to populate the reply message appropriately.
|
||||||
|
//! After returning, the caller will send this reply as a Mach message
|
||||||
|
//! via the message’s reply port.
|
||||||
|
//! \param[out] destroy_complex_request `true` if a complex request message
|
||||||
|
//! is to be destroyed even when handled successfully, `false`
|
||||||
|
//! otherwise. The traditional behavior is `false`. In this case, the
|
||||||
|
//! caller only destroys the request message in \a in when the reply
|
||||||
|
//! message in \a out is not complex and when it indicates a return code
|
||||||
|
//! other than `KERN_SUCCESS` or `MIG_NO_REPLY`. The assumption is that
|
||||||
|
//! the rights or out-of-line data carried in a complex message may be
|
||||||
|
//! retained by the server in this situation, and that it is the
|
||||||
|
//! responsibility of the server to release these resources as needed.
|
||||||
|
//! However, in many cases, these resources are not needed beyond the
|
||||||
|
//! duration of a request-reply transaction, and in such cases, it is
|
||||||
|
//! less error-prone to always have the caller,
|
||||||
|
//! MachMessageServer::Run(), destroy complex request messages. To
|
||||||
|
//! choose this behavior, this parameter should be set to `true`.
|
||||||
|
//!
|
||||||
|
//! \return `true` on success and `false` on failure, although the caller
|
||||||
|
//! ignores the return value. However, the return code to be included in
|
||||||
|
//! the reply message should be set as `mig_reply_error_t::RetCode`. The
|
||||||
|
//! non-`void` return value is used for increased compatibility with
|
||||||
|
//! MIG-generated functions.
|
||||||
|
virtual bool MachMessageServerFunction(mach_msg_header_t* in,
|
||||||
|
mach_msg_header_t* out,
|
||||||
|
bool* destroy_complex_request) = 0;
|
||||||
|
|
||||||
|
//! \return The expected or maximum size, in bytes, of a request message to
|
||||||
|
//! be received as the \a in parameter of MachMessageServerFunction().
|
||||||
|
virtual mach_msg_size_t MachMessageServerRequestSize() = 0;
|
||||||
|
|
||||||
|
//! \return The maximum size, in bytes, of a reply message to be sent via
|
||||||
|
//! the \a out parameter of MachMessageServerFunction(). This value does
|
||||||
|
//! not need to include the size of any trailer to be sent with the
|
||||||
|
//! message.
|
||||||
|
virtual mach_msg_size_t MachMessageServerReplySize() = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
~Interface() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
//! \brief Informs Run() whether to handle a single request-reply transaction
|
||||||
|
//! or to run in a loop.
|
||||||
|
enum Persistent {
|
||||||
|
//! \brief Handle a single request-reply transaction and then return.
|
||||||
|
kOneShot = false,
|
||||||
|
|
||||||
|
//! \brief Run in a loop, potentially handling multiple request-reply
|
||||||
|
//! transactions.
|
||||||
|
kPersistent,
|
||||||
|
};
|
||||||
|
|
||||||
|
//! \brief Informs Run() whether or not to block while waiting for requests.
|
||||||
|
enum Nonblocking {
|
||||||
|
//! \brief Wait for a request message if none is queued.
|
||||||
|
kBlocking = false,
|
||||||
|
|
||||||
|
//! \brief Return as soon as there no request messages queued. This may
|
||||||
|
//! result in an immediate return without handling any requests.
|
||||||
|
kNonblocking,
|
||||||
|
};
|
||||||
|
|
||||||
|
//! \brief Runs a Mach message server to handle a Mach RPC request for MIG
|
||||||
|
//! servers.
|
||||||
|
//!
|
||||||
|
//! This function listens for a request message and passes it to a callback
|
||||||
|
//! interface. A reponse is collected from that interface, and is sent back as
|
||||||
|
//! a reply.
|
||||||
|
//!
|
||||||
|
//! This function is similar to `mach_msg_server()` and
|
||||||
|
//! `mach_msg_server_once()`.
|
||||||
|
//!
|
||||||
|
//! \param[in] interface The MachMessageServerInterface that is responsible
|
||||||
|
//! for handling the message. Interface::MachMessageServerRequestSize() is
|
||||||
|
//! used as the receive size for the request message, and
|
||||||
|
//! Interface::MachMessageServerReplySize() is used as the
|
||||||
|
//! maximum size of the reply message. If \a options contains
|
||||||
|
//! `MACH_RCV_LARGE`, this function will retry a receive operation that
|
||||||
|
//! returns `MACH_RCV_TOO_LARGE` with an appropriately-sized buffer.
|
||||||
|
//! MachMessageServerInterface::MachMessageServerFunction() is called to
|
||||||
|
//! handle the request and populate the reply.
|
||||||
|
//! \param[in] receive_port The port on which to receive the request message.
|
||||||
|
//! \param[in] options Options suitable for mach_msg.
|
||||||
|
//! \param[in] persistent Chooses between one-shot and persistent operation.
|
||||||
|
//! \param[in] nonblocking Chooses between blocking and nonblocking operation.
|
||||||
|
//! \param[in] timeout_ms When \a nonblocking is `false`, the the maximum
|
||||||
|
//! duration that this entire function will run, in milliseconds, or
|
||||||
|
//! `MACH_MSG_TIMEOUT_NONE` to specify no timeout (infinite waiting). When
|
||||||
|
//! \a nonblocking is `true`, this parameter has no effect. When \a
|
||||||
|
//! persistent is `true`, the timeout applies to the overall duration of
|
||||||
|
//! this function, not to any individual `mach_msg()` call.
|
||||||
|
//!
|
||||||
|
//! \return On success, `KERN_SUCCESS` (when \a persistent is `false`) or
|
||||||
|
//! `MACH_RCV_TIMED_OUT` (when \a persistent and \a nonblocking are both
|
||||||
|
//! `true`, or when \a persistent is `true`, \a nonblocking is `false`,
|
||||||
|
//! and \a timeout is not `MACH_MSG_TIMEOUT_NONE`. This function has no
|
||||||
|
//! successful return value when \a persistent is `true`, \a nonblocking
|
||||||
|
//! is `false`, and \a timeout is `MACH_MSG_TIMEOUT_NONE`. On failure,
|
||||||
|
//! returns a value identifying the nature of the error.
|
||||||
|
static mach_msg_return_t Run(Interface* interface,
|
||||||
|
mach_port_t receive_port,
|
||||||
|
mach_msg_options_t options,
|
||||||
|
Persistent persistent,
|
||||||
|
Nonblocking nonblocking,
|
||||||
|
mach_msg_timeout_t timeout_ms);
|
||||||
|
|
||||||
|
private:
|
||||||
|
DISALLOW_IMPLICIT_CONSTRUCTORS(MachMessageServer);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace crashpad
|
||||||
|
|
||||||
|
#endif // CRASHPAD_UTIL_MACH_MACH_MESSAGE_SERVER_H_
|
517
util/mach/mach_message_server_test.cc
Normal file
517
util/mach/mach_message_server_test.cc
Normal file
@ -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
|
@ -61,6 +61,8 @@
|
|||||||
'mac/process_types/traits.h',
|
'mac/process_types/traits.h',
|
||||||
'mach/bootstrap.cc',
|
'mach/bootstrap.cc',
|
||||||
'mach/bootstrap.h',
|
'mach/bootstrap.h',
|
||||||
|
'mach/mach_message_server.cc',
|
||||||
|
'mach/mach_message_server.h',
|
||||||
'mach/task_memory.cc',
|
'mach/task_memory.cc',
|
||||||
'mach/task_memory.h',
|
'mach/task_memory.h',
|
||||||
'misc/initialization_state.h',
|
'misc/initialization_state.h',
|
||||||
@ -181,6 +183,7 @@
|
|||||||
'mac/process_types_test.cc',
|
'mac/process_types_test.cc',
|
||||||
'mac/service_management_test.mm',
|
'mac/service_management_test.mm',
|
||||||
'mach/bootstrap_test.cc',
|
'mach/bootstrap_test.cc',
|
||||||
|
'mach/mach_message_server_test.cc',
|
||||||
'mach/task_memory_test.cc',
|
'mach/task_memory_test.cc',
|
||||||
'misc/initialization_state_dcheck_test.cc',
|
'misc/initialization_state_dcheck_test.cc',
|
||||||
'misc/initialization_state_test.cc',
|
'misc/initialization_state_test.cc',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user