Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(134)

Side by Side Diff: util/mach/mach_message_server_test.cc

Issue 544393002: Add MachMessageServer and its test (Closed) Base URL: https://chromium.googlesource.com/crashpad/crashpad@master
Patch Set: Address review feedback Created 6 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2014 The Crashpad Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "util/mach/mach_message_server.h"
16
17 #include <mach/mach.h>
18 #include <string.h>
19
20 #include "base/basictypes.h"
21 #include "base/mac/scoped_mach_port.h"
22 #include "gtest/gtest.h"
23 #include "util/file/fd_io.h"
24 #include "util/test/errors.h"
25 #include "util/test/mac/mach_errors.h"
26 #include "util/test/mac/mach_multiprocess.h"
27
28 namespace {
29
30 using namespace crashpad;
31 using namespace crashpad::test;
32
33 class TestMachMessageServer : public MachMessageServer::Interface,
34 public MachMultiprocess {
35 public:
36 struct Options {
37 // The type of reply port that the client should put in its request message.
38 enum ReplyPortType {
39 // The normal reply port is the client’s local port, to which it holds
40 // a receive right. This allows the server to respond directly to the
41 // client. The client will expect a reply.
42 kReplyPortNormal,
43
44 // Use MACH_PORT_NULL as the reply port, which the server should detect
45 // avoid attempting to send a message to, and return success. The client
46 // will not expect a reply.
47 kReplyPortNull,
48
49 // Make the server see the reply port as a dead name by setting the reply
50 // port to a receive right and then destroying that right before the
51 // server processes the request. The server should return
52 // MACH_SEND_INVALID_DEST, and the client will not expect a reply.
53 kReplyPortDead,
54 };
55
56 Options()
57 : expect_server_interface_method_called(true),
58 parent_wait_for_child_pipe(false),
59 server_persistent(MachMessageServer::kOneShot),
60 server_nonblocking(MachMessageServer::kBlocking),
61 server_timeout_ms(MACH_MSG_TIMEOUT_NONE),
62 server_mig_retcode(KERN_SUCCESS),
63 expect_server_result(KERN_SUCCESS),
64 client_send_request_count(1),
65 client_reply_port_type(kReplyPortNormal),
66 child_send_all_requests_before_receiving_any_replies(false) {
67 }
68
69 // true if MachMessageServerFunction() is expected to be called.
70 bool expect_server_interface_method_called;
71
72 // true if the parent should wait for the child to write a byte to the pipe
73 // as a signal that the child is ready for the parent to begin its side of
74 // the test. This is used for nonblocking tests, which require that there
75 // be something in the server’s queue before attempting a nonblocking
76 // receive if the receive is to be successful.
77 bool parent_wait_for_child_pipe;
78
79 // Whether the server should run in one-shot or persistent mode.
80 MachMessageServer::Persistent server_persistent;
81
82 // Whether the server should run in blocking or nonblocking mode.
83 MachMessageServer::Nonblocking server_nonblocking;
84
85 // The server’s timeout.
86 mach_msg_timeout_t server_timeout_ms;
87
88 // The return code that the server returns to the client via the
89 // mig_reply_error_t::RetCode field. A client would normally see this as
90 // a Mach RPC return value.
91 kern_return_t server_mig_retcode;
92
93 // The expected return value from MachMessageServer::Run().
94 kern_return_t expect_server_result;
95
96 // The number of requests that the client should send to the server.
97 size_t client_send_request_count;
98
99 // The type of reply port that the client should provide in its request’s
100 // mach_msg_header_t::msgh_local_port, which will appear to the server as
101 // mach_msg_header_t::msgh_remote_port.
102 ReplyPortType client_reply_port_type;
103
104 // true if the client should send all requests before attempting to receive
105 // any replies from the server. This is used for the persistent nonblocking
106 // test, which requires the client to fill the server’s queue before the
107 // server can attempt processing it.
108 bool child_send_all_requests_before_receiving_any_replies;
109 };
110
111 explicit TestMachMessageServer(const Options& options)
112 : MachMessageServer::Interface(),
113 MachMultiprocess(),
114 options_(options) {
115 }
116
117 // Runs the test.
118 void Test() {
119 EXPECT_EQ(requests_, replies_);
120 uint32_t start = requests_;
121
122 Run();
123
124 EXPECT_EQ(requests_, replies_);
125 EXPECT_EQ(options_.client_send_request_count, requests_ - start);
126 }
127
128 // MachMessageServerInterface:
129
130 virtual bool MachMessageServerFunction(
131 mach_msg_header_t* in,
132 mach_msg_header_t* out,
133 bool* destroy_complex_request) override {
134 *destroy_complex_request = true;
135
136 EXPECT_TRUE(options_.expect_server_interface_method_called);
137 if (!options_.expect_server_interface_method_called) {
138 return false;
139 }
140
141 struct ReceiveRequestMessage : public RequestMessage {
142 mach_msg_trailer_t trailer;
143 };
144
145 const ReceiveRequestMessage* request =
146 reinterpret_cast<ReceiveRequestMessage*>(in);
147 EXPECT_EQ(static_cast<mach_msg_bits_t>(
148 MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND, MACH_MSG_TYPE_MOVE_SEND)),
149 request->header.msgh_bits);
150 EXPECT_EQ(sizeof(RequestMessage), request->header.msgh_size);
151 if (options_.client_reply_port_type == Options::kReplyPortNormal) {
152 EXPECT_EQ(RemotePort(), request->header.msgh_remote_port);
153 }
154 EXPECT_EQ(LocalPort(), request->header.msgh_local_port);
155 EXPECT_EQ(kRequestMessageId, request->header.msgh_id);
156 EXPECT_EQ(0, memcmp(&request->ndr, &NDR_record, sizeof(NDR_record)));
157 EXPECT_EQ(requests_, request->number);
158 EXPECT_EQ(static_cast<mach_msg_trailer_type_t>(MACH_MSG_TRAILER_FORMAT_0),
159 request->trailer.msgh_trailer_type);
160 EXPECT_EQ(MACH_MSG_TRAILER_MINIMUM_SIZE,
161 request->trailer.msgh_trailer_size);
162
163 ++requests_;
164
165 ReplyMessage* reply = reinterpret_cast<ReplyMessage*>(out);
166 reply->Head.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
167 reply->Head.msgh_size = sizeof(*reply);
168 reply->Head.msgh_remote_port = request->header.msgh_remote_port;
169 reply->Head.msgh_local_port = MACH_PORT_NULL;
170 reply->Head.msgh_id = kReplyMessageId;
171 reply->NDR = NDR_record;
172 reply->RetCode = options_.server_mig_retcode;
173 reply->number = replies_++;
174
175 return true;
176 }
177
178 virtual mach_msg_size_t MachMessageServerRequestSize() override {
179 return sizeof(RequestMessage);
180 }
181
182 virtual mach_msg_size_t MachMessageServerReplySize() override {
183 return sizeof(ReplyMessage);
184 }
185
186 private:
187 struct RequestMessage {
188 mach_msg_header_t header;
189 NDR_record_t ndr;
190 uint32_t number;
191 };
192
193 struct ReplyMessage : public mig_reply_error_t {
194 uint32_t number;
195 };
196
197 // MachMultiprocess:
198
199 virtual void MachMultiprocessParent() override {
200 if (options_.parent_wait_for_child_pipe) {
201 // Wait until the child is done sending what it’s going to send.
202 char c;
203 ssize_t rv = ReadFD(ReadPipeFD(), &c, 1);
204 EXPECT_EQ(1, rv) << ErrnoMessage("read");
205 EXPECT_EQ('\0', c);
206 }
207
208 kern_return_t kr;
209 ASSERT_EQ(options_.expect_server_result,
210 (kr = MachMessageServer::Run(this,
211 LocalPort(),
212 MACH_MSG_OPTION_NONE,
213 options_.server_persistent,
214 options_.server_nonblocking,
215 options_.server_timeout_ms)))
216 << MachErrorMessage(kr, "MachMessageServer");
217 }
218
219 virtual void MachMultiprocessChild() override {
220 for (size_t index = 0;
221 index < options_.client_send_request_count;
222 ++index) {
223 if (options_.child_send_all_requests_before_receiving_any_replies) {
224 // For this test, all of the messages need to go into the queue before
225 // the parent is allowed to start processing them. Don’t attempt to
226 // process replies before all of the requests are sent, because the
227 // server won’t have sent any replies until all of the requests are in
228 // its queue.
229 ChildSendRequest();
230 } else {
231 ChildSendRequestAndWaitForReply();
232 }
233 if (testing::Test::HasFatalFailure()) {
234 return;
235 }
236 }
237
238 if (options_.parent_wait_for_child_pipe &&
239 options_.child_send_all_requests_before_receiving_any_replies) {
240 // Now that all of the requests have been sent, let the parent know that
241 // it’s safe to begin processing them, and then wait for the replies.
242 ChildNotifyParentViaPipe();
243 if (testing::Test::HasFatalFailure()) {
244 return;
245 }
246
247 for (size_t index = 0;
248 index < options_.client_send_request_count;
249 ++index) {
250 ChildWaitForReply();
251 if (testing::Test::HasFatalFailure()) {
252 return;
253 }
254 }
255 }
256 }
257
258 // In the child process, sends a request message to the server.
259 void ChildSendRequest() {
260 // local_receive_port_owner will the receive right that is created in this
261 // scope and intended to be destroyed when leaving this scope, after it has
262 // been carried in a Mach message.
263 base::mac::ScopedMachReceiveRight local_receive_port_owner;
264
265 RequestMessage request = {};
266 request.header.msgh_bits =
267 MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND);
268 request.header.msgh_size = sizeof(request);
269 request.header.msgh_remote_port = RemotePort();
270 kern_return_t kr;
271 switch (options_.client_reply_port_type) {
272 case Options::kReplyPortNormal:
273 request.header.msgh_local_port = LocalPort();
274 break;
275 case Options::kReplyPortNull:
276 request.header.msgh_local_port = MACH_PORT_NULL;
277 break;
278 case Options::kReplyPortDead: {
279 // Use a newly-allocated receive right that will be destroyed when this
280 // method returns. A send right will be made from this receive right and
281 // carried in the request message to the server. By the time the server
282 // looks at the right, it will have become a dead name.
283 kr = mach_port_allocate(mach_task_self(),
284 MACH_PORT_RIGHT_RECEIVE,
285 &request.header.msgh_local_port);
286 ASSERT_EQ(KERN_SUCCESS, kr)
287 << MachErrorMessage(kr, "mach_port_allocate");
288 local_receive_port_owner.reset(request.header.msgh_local_port);
289 break;
290 }
291 }
292 request.header.msgh_id = kRequestMessageId;
293 request.number = requests_++;
294 request.ndr = NDR_record;
295
296 kr = mach_msg(&request.header,
297 MACH_SEND_MSG | MACH_SEND_TIMEOUT,
298 request.header.msgh_size,
299 0,
300 MACH_PORT_NULL,
301 MACH_MSG_TIMEOUT_NONE,
302 MACH_PORT_NULL);
303 ASSERT_EQ(MACH_MSG_SUCCESS, kr) << MachErrorMessage(kr, "mach_msg");
304 }
305
306 // In the child process, waits for a reply message from the server.
307 void ChildWaitForReply() {
308 if (options_.client_reply_port_type != Options::kReplyPortNormal) {
309 // The client shouldn’t expect a reply when it didn’t send a good reply
310 // port with its request.
311 return;
312 }
313
314 struct ReceiveReplyMessage : public ReplyMessage {
315 mach_msg_trailer_t trailer;
316 };
317
318 ReceiveReplyMessage reply = {};
319 kern_return_t kr = mach_msg(&reply.Head,
320 MACH_RCV_MSG,
321 0,
322 sizeof(reply),
323 LocalPort(),
324 MACH_MSG_TIMEOUT_NONE,
325 MACH_PORT_NULL);
326 ASSERT_EQ(MACH_MSG_SUCCESS, kr) << MachErrorMessage(kr, "mach_msg");
327
328 ASSERT_EQ(static_cast<mach_msg_bits_t>(
329 MACH_MSGH_BITS(0, MACH_MSG_TYPE_MOVE_SEND)), reply.Head.msgh_bits);
330 ASSERT_EQ(sizeof(ReplyMessage), reply.Head.msgh_size);
331 ASSERT_EQ(static_cast<mach_port_t>(MACH_PORT_NULL),
332 reply.Head.msgh_remote_port);
333 ASSERT_EQ(LocalPort(), reply.Head.msgh_local_port);
334 ASSERT_EQ(kReplyMessageId, reply.Head.msgh_id);
335 ASSERT_EQ(0, memcmp(&reply.NDR, &NDR_record, sizeof(NDR_record)));
336 ASSERT_EQ(options_.server_mig_retcode, reply.RetCode);
337 ASSERT_EQ(replies_, reply.number);
338 ASSERT_EQ(static_cast<mach_msg_trailer_type_t>(MACH_MSG_TRAILER_FORMAT_0),
339 reply.trailer.msgh_trailer_type);
340 ASSERT_EQ(MACH_MSG_TRAILER_MINIMUM_SIZE, reply.trailer.msgh_trailer_size);
341
342 ++replies_;
343 }
344
345 // For test types where the child needs to notify the server in the parent
346 // that the child is ready, this method will send a byte via the POSIX pipe.
347 // The parent will be waiting in a read() on this pipe, and will proceed to
348 // running MachMessageServer() once it’s received.
349 void ChildNotifyParentViaPipe() {
350 char c = '\0';
351 ssize_t rv = WriteFD(WritePipeFD(), &c, 1);
352 ASSERT_EQ(1, rv) << ErrnoMessage("write");
353 }
354
355 // In the child process, sends a request message to the server and then
356 // receives a reply message.
357 void ChildSendRequestAndWaitForReply() {
358 ChildSendRequest();
359 if (testing::Test::HasFatalFailure()) {
360 return;
361 }
362
363 if (options_.parent_wait_for_child_pipe &&
364 !options_.child_send_all_requests_before_receiving_any_replies) {
365 // The parent is waiting to read a byte to indicate that the message has
366 // been placed in the queue.
367 ChildNotifyParentViaPipe();
368 if (testing::Test::HasFatalFailure()) {
369 return;
370 }
371 }
372
373 ChildWaitForReply();
374 }
375
376 const Options& options_;
377
378 static uint32_t requests_;
379 static uint32_t replies_;
380
381 static const mach_msg_id_t kRequestMessageId = 16237;
382 static const mach_msg_id_t kReplyMessageId = kRequestMessageId + 100;
383
384 DISALLOW_COPY_AND_ASSIGN(TestMachMessageServer);
385 };
386
387 uint32_t TestMachMessageServer::requests_;
388 uint32_t TestMachMessageServer::replies_;
389 const mach_msg_id_t TestMachMessageServer::kRequestMessageId;
390 const mach_msg_id_t TestMachMessageServer::kReplyMessageId;
391
392 TEST(MachMessageServer, Basic) {
393 // The client sends one message to the server, which will wait indefinitely in
394 // blocking mode for it.
395 TestMachMessageServer::Options options;
396 TestMachMessageServer test_mach_message_server(options);
397 test_mach_message_server.Test();
398 }
399
400 TEST(MachMessageServer, NonblockingNoMessage) {
401 // The server waits in nonblocking mode and the client sends nothing, so the
402 // server should return immediately without processing any message.
403 TestMachMessageServer::Options options;
404 options.expect_server_interface_method_called = false;
405 options.server_nonblocking = MachMessageServer::kNonblocking;
406 options.expect_server_result = MACH_RCV_TIMED_OUT;
407 options.client_send_request_count = 0;
408 TestMachMessageServer test_mach_message_server(options);
409 test_mach_message_server.Test();
410 }
411
412 TEST(MachMessageServer, TimeoutNoMessage) {
413 // The server waits in blocking mode for one message, but with a timeout. The
414 // client sends no message, so the server returns after the timeout.
415 TestMachMessageServer::Options options;
416 options.expect_server_interface_method_called = false;
417 options.server_timeout_ms = 10;
418 options.expect_server_result = MACH_RCV_TIMED_OUT;
419 options.client_send_request_count = 0;
420 TestMachMessageServer test_mach_message_server(options);
421 test_mach_message_server.Test();
422 }
423
424 TEST(MachMessageServer, Nonblocking) {
425 // The client sends one message to the server and then signals the server that
426 // it’s safe to start waiting for it in nonblocking mode. The message is in
427 // the server’s queue, so it’s able to receive it when it begins listening in
428 // nonblocking mode.
429 TestMachMessageServer::Options options;
430 options.parent_wait_for_child_pipe = true;
431 options.server_nonblocking = MachMessageServer::kNonblocking;
432 TestMachMessageServer test_mach_message_server(options);
433 test_mach_message_server.Test();
434 }
435
436 TEST(MachMessageServer, Timeout) {
437 // The client sends one message to the server, which will wait in blocking
438 // mode for it up to a specific timeout.
439 TestMachMessageServer::Options options;
440 options.server_timeout_ms = 10;
441 TestMachMessageServer test_mach_message_server(options);
442 test_mach_message_server.Test();
443 }
444
445 TEST(MachMessageServer, PersistentTenMessages) {
446 // The server waits for as many messages as it can receive in blocking mode
447 // with a timeout. The client sends several messages, and the server processes
448 // them all.
449 TestMachMessageServer::Options options;
450 options.server_persistent = MachMessageServer::kPersistent;
451 options.server_timeout_ms = 10;
452 options.expect_server_result = MACH_RCV_TIMED_OUT;
453 options.client_send_request_count = 10;
454 TestMachMessageServer test_mach_message_server(options);
455 test_mach_message_server.Test();
456 }
457
458 TEST(MachMessageServer, PersistentNonblockingFourMessages) {
459 // The client sends several messages to the server and then signals the server
460 // that it’s safe to start waiting for them in nonblocking mode. The server
461 // then listens for them in nonblocking persistent mode, and receives all of
462 // them because they’ve been queued up. The client doesn’t wait for the
463 // replies until after it’s put all of its requests into the server’s queue.
464 //
465 // This test is sensitive to the length of the IPC queue limit. Mach ports
466 // normally have a queue length limit of MACH_PORT_QLIMIT_DEFAULT (which is
467 // MACH_PORT_QLIMIT_BASIC, or 5). The number of messages sent for this test
468 // must be below this, because the server does not begin dequeueing request
469 // messages until the client has finished sending them.
470 TestMachMessageServer::Options options;
471 options.parent_wait_for_child_pipe = true;
472 options.server_persistent = MachMessageServer::kPersistent;
473 options.server_nonblocking = MachMessageServer::kNonblocking;
474 options.expect_server_result = MACH_RCV_TIMED_OUT;
475 options.client_send_request_count = 4;
476 options.child_send_all_requests_before_receiving_any_replies = true;
477 TestMachMessageServer test_mach_message_server(options);
478 test_mach_message_server.Test();
479 }
480
481 TEST(MachMessageServer, ReturnCodeInvalidArgument) {
482 // This tests that the mig_reply_error_t::RetCode field is properly returned
483 // to the client.
484 TestMachMessageServer::Options options;
485 TestMachMessageServer test_mach_message_server(options);
486 options.server_mig_retcode = KERN_INVALID_ARGUMENT;
487 test_mach_message_server.Test();
488 }
489
490 TEST(MachMessageServer, ReplyPortNull) {
491 // The client sets its reply port to MACH_PORT_NULL. The server should see
492 // this and avoid sending a message to the null port. No reply message is
493 // sent and the server returns success.
494 TestMachMessageServer::Options options;
495 TestMachMessageServer test_mach_message_server(options);
496 options.client_reply_port_type =
497 TestMachMessageServer::Options::kReplyPortNull;
498 test_mach_message_server.Test();
499 }
500
501 TEST(MachMessageServer, ReplyPortDead) {
502 // The client allocates a new port and uses it as the reply port in its
503 // request message, and then deallocates its receive right to that port. It
504 // then signals the server to process the request message. The server’s view
505 // of the port is that it is a dead name. The server function will return
506 // MACH_SEND_INVALID_DEST because it’s not possible to send a message to a
507 // dead name.
508 TestMachMessageServer::Options options;
509 TestMachMessageServer test_mach_message_server(options);
510 options.parent_wait_for_child_pipe = true;
511 options.expect_server_result = MACH_SEND_INVALID_DEST;
512 options.client_reply_port_type =
513 TestMachMessageServer::Options::kReplyPortDead;
514 test_mach_message_server.Test();
515 }
516
517 } // namespace
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698