1use std::ffi::{CString, c_char, c_int, c_void};
4use std::fmt;
5use std::mem::{size_of, zeroed};
6
7use super::{KERN_SUCCESS, MachPort, current_task, deallocate_port};
8
9type MachMsgReturn = c_int;
10type PosixSpawnAttr = *mut c_void;
11
12const MACH_PORT_NULL: MachPort = 0;
13const MACH_PORT_RIGHT_RECEIVE: c_int = 1;
14const MACH_MSG_TYPE_COPY_SEND: u8 = 19;
15const MACH_MSG_TYPE_MAKE_SEND: u8 = 20;
16const MACH_MSG_TYPE_PORT_SEND: u8 = 17;
17const MACH_MSG_PORT_DESCRIPTOR: u8 = 0;
18const MACH_MSGH_BITS_COMPLEX: u32 = 0x8000_0000;
19const MACH_SEND_MSG: u32 = 0x0000_0001;
20const MACH_RCV_MSG: u32 = 0x0000_0002;
21const MACH_SEND_TIMEOUT: u32 = 0x0000_0010;
22const MACH_RCV_TIMEOUT: u32 = 0x0000_0100;
23const MACH_RCV_TRAILER_AUDIT: u32 = 3 << 24;
24const TASK_BOOTSTRAP_PORT: c_int = 4;
25const MESSAGE_ID: c_int = 0x4e49_5043;
26const MESSAGE_MAGIC: [u8; 8] = *b"NIPCMACH";
27const ENV_NONCE: &str = "NATIVE_IPC_MACH_NONCE";
28const ENV_PARENT_PID: &str = "NATIVE_IPC_PARENT_PID";
29const TIMEOUT_MS: u32 = 10_000;
30
31unsafe extern "C" {
32 fn mach_port_allocate(task: MachPort, right: c_int, name: *mut MachPort) -> c_int;
33 fn mach_port_insert_right(
34 task: MachPort,
35 name: MachPort,
36 poly: MachPort,
37 poly_poly: c_int,
38 ) -> c_int;
39 fn mach_port_mod_refs(task: MachPort, name: MachPort, right: c_int, delta: c_int) -> c_int;
40 fn mach_msg(
41 message: *mut MachMsgHeader,
42 option: u32,
43 send_size: u32,
44 receive_limit: u32,
45 receive_name: MachPort,
46 timeout: u32,
47 notify: MachPort,
48 ) -> MachMsgReturn;
49 fn mach_msg_destroy(message: *mut MachMsgHeader);
50 fn task_get_special_port(task: MachPort, which: c_int, port: *mut MachPort) -> c_int;
51 fn posix_spawnattr_init(attributes: *mut PosixSpawnAttr) -> c_int;
52 fn posix_spawnattr_destroy(attributes: *mut PosixSpawnAttr) -> c_int;
53 fn posix_spawnattr_setspecialport_np(
54 attributes: *mut PosixSpawnAttr,
55 port: MachPort,
56 which: c_int,
57 ) -> c_int;
58 fn posix_spawn(
59 pid: *mut Pid,
60 path: *const c_char,
61 file_actions: *const c_void,
62 attributes: *const PosixSpawnAttr,
63 argv: *const *mut c_char,
64 envp: *const *mut c_char,
65 ) -> c_int;
66 fn kill(pid: Pid, signal: c_int) -> c_int;
67 fn waitpid(pid: Pid, status: *mut c_int, options: c_int) -> Pid;
68}
69
70#[link(name = "bsm")]
71unsafe extern "C" {
72 fn audit_token_to_pid(token: AuditToken) -> Pid;
73}
74
75type Pid = c_int;
76
77#[repr(C)]
78#[derive(Clone, Copy)]
79struct MachMsgHeader {
80 bits: u32,
81 size: u32,
82 remote_port: MachPort,
83 local_port: MachPort,
84 voucher_port: MachPort,
85 id: c_int,
86}
87
88#[repr(C)]
89#[derive(Clone, Copy)]
90struct MachMsgBody {
91 descriptor_count: u32,
92}
93
94#[repr(C)]
95#[derive(Clone, Copy)]
96struct MachMsgPortDescriptor {
97 name: MachPort,
98 pad1: u32,
99 pad2: u16,
100 disposition: u8,
101 descriptor_type: u8,
102}
103
104#[repr(C)]
105#[derive(Clone, Copy)]
106struct AuditToken {
107 values: [u32; 8],
108}
109
110#[repr(C)]
111#[derive(Clone, Copy)]
112struct AuditTrailer {
113 trailer_type: u32,
114 trailer_size: u32,
115 sequence: u32,
116 sender_security: [u32; 2],
117 audit: AuditToken,
118}
119
120#[repr(C)]
121struct PortMessage {
122 header: MachMsgHeader,
123 body: MachMsgBody,
124 descriptor: MachMsgPortDescriptor,
125 magic: [u8; 8],
126 nonce: [u8; 32],
127}
128
129#[repr(C)]
130struct ReceiveBuffer {
131 message: PortMessage,
132 trailer: AuditTrailer,
133}
134
135#[derive(Debug)]
137pub enum BootstrapError {
138 Mach {
140 operation: &'static str,
142 code: c_int,
144 },
145 Spawn(c_int),
147 InvalidMessage,
149 WrongPeer {
151 expected: u32,
153 actual: u32,
155 },
156 InvalidEnvironment,
158}
159
160impl fmt::Display for BootstrapError {
161 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
162 write!(formatter, "Mach bootstrap failed: {self:?}")
163 }
164}
165impl std::error::Error for BootstrapError {}
166
167pub struct SendRight(MachPort);
169impl SendRight {
170 pub(super) const fn name(&self) -> MachPort {
172 self.0
173 }
174}
175impl Drop for SendRight {
176 fn drop(&mut self) {
177 deallocate_port(current_task(), self.0);
178 }
179}
180
181struct ReceiveRight(MachPort);
182impl ReceiveRight {
183 fn allocate() -> Result<Self, BootstrapError> {
184 let mut name = MACH_PORT_NULL;
185 let result =
187 unsafe { mach_port_allocate(current_task(), MACH_PORT_RIGHT_RECEIVE, &mut name) };
188 mach("mach_port_allocate", result)?;
189 if name == MACH_PORT_NULL {
190 return Err(BootstrapError::InvalidMessage);
191 }
192 Ok(Self(name))
193 }
194 fn make_send(&self) -> Result<(), BootstrapError> {
195 mach("mach_port_insert_right", unsafe {
197 mach_port_insert_right(
198 current_task(),
199 self.0,
200 self.0,
201 MACH_MSG_TYPE_MAKE_SEND.into(),
202 )
203 })
204 }
205}
206impl Drop for ReceiveRight {
207 fn drop(&mut self) {
208 let _ = unsafe { mach_port_mod_refs(current_task(), self.0, MACH_PORT_RIGHT_RECEIVE, -1) };
210 }
211}
212
213pub struct SpawnedHelper {
215 pid: Pid,
216 nonce: [u8; 32],
217 receive: Option<ReceiveRight>,
218}
219
220impl SpawnedHelper {
221 pub fn spawn(path: &CString, arguments: &[CString]) -> Result<Self, BootstrapError> {
223 let nonce = random_nonce()?;
224 let receive = ReceiveRight::allocate()?;
225 receive.make_send()?;
226 let mut attributes: PosixSpawnAttr = std::ptr::null_mut();
227 spawn_result(unsafe { posix_spawnattr_init(&mut attributes) })?;
229 struct AttributeGuard(PosixSpawnAttr);
230 impl Drop for AttributeGuard {
231 fn drop(&mut self) {
232 let _ = unsafe { posix_spawnattr_destroy(&mut self.0) };
234 }
235 }
236 let mut guard = AttributeGuard(attributes);
237 spawn_result(unsafe {
239 posix_spawnattr_setspecialport_np(&mut guard.0, receive.0, TASK_BOOTSTRAP_PORT)
240 })?;
241
242 let mut argv_storage = Vec::with_capacity(arguments.len() + 1);
243 argv_storage.push(path.clone());
244 argv_storage.extend(arguments.iter().cloned());
245 let mut argv: Vec<*mut c_char> = argv_storage
246 .iter_mut()
247 .map(|argument| argument.as_ptr().cast_mut())
248 .collect();
249 argv.push(std::ptr::null_mut());
250
251 let nonce_value = hex(&nonce);
252 let parent_pid = std::process::id().to_string();
253 let mut environment: Vec<CString> = std::env::vars_os()
254 .filter(|(key, _)| key != ENV_NONCE && key != ENV_PARENT_PID)
255 .filter_map(|(key, value)| {
256 CString::new(format!(
257 "{}={}",
258 key.to_string_lossy(),
259 value.to_string_lossy()
260 ))
261 .ok()
262 })
263 .collect();
264 environment.push(CString::new(format!("{ENV_NONCE}={nonce_value}")).expect("hex env"));
265 environment.push(CString::new(format!("{ENV_PARENT_PID}={parent_pid}")).expect("pid env"));
266 let mut envp: Vec<*mut c_char> = environment
267 .iter_mut()
268 .map(|entry| entry.as_ptr().cast_mut())
269 .collect();
270 envp.push(std::ptr::null_mut());
271 let mut pid = 0;
272 let result = unsafe {
274 posix_spawn(
275 &mut pid,
276 path.as_ptr(),
277 std::ptr::null(),
278 &guard.0,
279 argv.as_ptr(),
280 envp.as_ptr(),
281 )
282 };
283 spawn_result(result)?;
284 deallocate_port(current_task(), receive.0);
286 Ok(Self {
287 pid,
288 nonce,
289 receive: Some(receive),
290 })
291 }
292
293 pub fn authenticate(mut self) -> Result<ParentChannel, BootstrapError> {
295 let receive = self.receive.take().ok_or(BootstrapError::InvalidMessage)?;
296 let child_send = match receive_port(&receive, &self.nonce, self.pid as u32) {
297 Ok(right) => right,
298 Err(error) => {
299 terminate_and_reap(self.pid);
300 self.pid = 0;
301 return Err(error);
302 }
303 };
304 let channel = ParentChannel {
305 peer_send: child_send,
306 _receive: receive,
307 nonce: self.nonce,
308 peer_pid: self.pid as u32,
309 reaped: false,
310 };
311 self.pid = 0;
312 Ok(channel)
313 }
314
315 pub const fn pid(&self) -> u32 {
317 self.pid as u32
318 }
319}
320
321impl Drop for SpawnedHelper {
322 fn drop(&mut self) {
323 if self.pid > 0 {
324 terminate_and_reap(self.pid);
325 }
326 }
327}
328
329impl Drop for ParentChannel {
330 fn drop(&mut self) {
331 if !self.reaped {
332 terminate_and_reap(self.peer_pid as Pid);
333 }
334 }
335}
336
337pub struct ParentChannel {
339 peer_send: SendRight,
340 _receive: ReceiveRight,
341 nonce: [u8; 32],
342 peer_pid: u32,
343 reaped: bool,
344}
345
346impl ParentChannel {
347 pub(super) fn send(&self, port: MachPort) -> Result<(), BootstrapError> {
349 send_port(self.peer_send.0, port, MACH_MSG_TYPE_COPY_SEND, &self.nonce)
350 }
351 pub const fn peer_pid(&self) -> u32 {
353 self.peer_pid
354 }
355 pub fn wait_ready(&self) -> Result<(), BootstrapError> {
357 drop(receive_port(&self._receive, &self.nonce, self.peer_pid)?);
358 Ok(())
359 }
360 pub fn wait(mut self) -> Result<(), BootstrapError> {
362 let mut status = 0;
363 let result = unsafe { waitpid(self.peer_pid as Pid, &mut status, 0) };
365 self.reaped = result == self.peer_pid as Pid;
366 if self.reaped && status == 0 {
367 Ok(())
368 } else {
369 Err(BootstrapError::Spawn(status))
370 }
371 }
372}
373
374pub struct ChildChannel {
376 _parent_send: SendRight,
377 receive: ReceiveRight,
378 nonce: [u8; 32],
379 parent_pid: u32,
380}
381
382impl ChildChannel {
383 pub fn connect_from_environment() -> Result<Self, BootstrapError> {
385 let nonce = parse_nonce(
386 &std::env::var(ENV_NONCE).map_err(|_| BootstrapError::InvalidEnvironment)?,
387 )?;
388 let parent_pid = std::env::var(ENV_PARENT_PID)
389 .map_err(|_| BootstrapError::InvalidEnvironment)?
390 .parse()
391 .map_err(|_| BootstrapError::InvalidEnvironment)?;
392 let mut parent = MACH_PORT_NULL;
393 mach("task_get_special_port", unsafe {
395 task_get_special_port(current_task(), TASK_BOOTSTRAP_PORT, &mut parent)
396 })?;
397 if parent == MACH_PORT_NULL {
398 return Err(BootstrapError::InvalidEnvironment);
399 }
400 let receive = ReceiveRight::allocate()?;
401 send_port(parent, receive.0, MACH_MSG_TYPE_MAKE_SEND, &nonce)?;
402 Ok(Self {
403 _parent_send: SendRight(parent),
404 receive,
405 nonce,
406 parent_pid,
407 })
408 }
409 pub(super) fn receive(&self) -> Result<SendRight, BootstrapError> {
411 receive_port(&self.receive, &self.nonce, self.parent_pid)
412 }
413 pub fn signal_ready(&self) -> Result<(), BootstrapError> {
415 let marker = ReceiveRight::allocate()?;
416 send_port(
417 self._parent_send.0,
418 marker.0,
419 MACH_MSG_TYPE_MAKE_SEND,
420 &self.nonce,
421 )
422 }
423}
424
425fn send_port(
426 remote: MachPort,
427 port: MachPort,
428 disposition: u8,
429 nonce: &[u8; 32],
430) -> Result<(), BootstrapError> {
431 let mut message = PortMessage {
432 header: MachMsgHeader {
433 bits: MACH_MSGH_BITS_COMPLEX | u32::from(MACH_MSG_TYPE_COPY_SEND),
434 size: size_of::<PortMessage>() as u32,
435 remote_port: remote,
436 local_port: MACH_PORT_NULL,
437 voucher_port: MACH_PORT_NULL,
438 id: MESSAGE_ID,
439 },
440 body: MachMsgBody {
441 descriptor_count: 1,
442 },
443 descriptor: MachMsgPortDescriptor {
444 name: port,
445 pad1: 0,
446 pad2: 0,
447 disposition,
448 descriptor_type: MACH_MSG_PORT_DESCRIPTOR,
449 },
450 magic: MESSAGE_MAGIC,
451 nonce: *nonce,
452 };
453 mach("mach_msg(send)", unsafe {
455 mach_msg(
456 &mut message.header,
457 MACH_SEND_MSG | MACH_SEND_TIMEOUT,
458 size_of::<PortMessage>() as u32,
459 0,
460 MACH_PORT_NULL,
461 TIMEOUT_MS,
462 MACH_PORT_NULL,
463 )
464 })
465}
466
467fn receive_port(
468 receive: &ReceiveRight,
469 nonce: &[u8; 32],
470 expected_pid: u32,
471) -> Result<SendRight, BootstrapError> {
472 let mut buffer: ReceiveBuffer = unsafe { zeroed() };
474 mach("mach_msg(receive)", unsafe {
476 mach_msg(
477 &mut buffer.message.header,
478 MACH_RCV_MSG | MACH_RCV_TIMEOUT | MACH_RCV_TRAILER_AUDIT,
479 0,
480 size_of::<ReceiveBuffer>() as u32,
481 receive.0,
482 TIMEOUT_MS,
483 MACH_PORT_NULL,
484 )
485 })?;
486 let complex = buffer.message.header.bits & MACH_MSGH_BITS_COMPLEX != 0;
487 if buffer.message.header.size as usize != size_of::<PortMessage>()
488 || !complex
489 || buffer.message.header.id != MESSAGE_ID
490 || buffer.message.body.descriptor_count != 1
491 || buffer.message.descriptor.descriptor_type != MACH_MSG_PORT_DESCRIPTOR
492 || buffer.message.descriptor.disposition != MACH_MSG_TYPE_PORT_SEND
493 || buffer.message.magic != MESSAGE_MAGIC
494 || buffer.message.nonce != *nonce
495 || buffer.message.descriptor.name == MACH_PORT_NULL
496 || buffer.trailer.trailer_size as usize != size_of::<AuditTrailer>()
497 {
498 if complex {
499 unsafe { mach_msg_destroy(&mut buffer.message.header) };
502 }
503 return Err(BootstrapError::InvalidMessage);
504 }
505 let actual = unsafe { audit_token_to_pid(buffer.trailer.audit) } as u32;
507 if actual != expected_pid {
508 deallocate_port(current_task(), buffer.message.descriptor.name);
509 return Err(BootstrapError::WrongPeer {
510 expected: expected_pid,
511 actual,
512 });
513 }
514 Ok(SendRight(buffer.message.descriptor.name))
515}
516
517fn random_nonce() -> Result<[u8; 32], BootstrapError> {
518 let mut nonce = [0_u8; 32];
519 unsafe extern "C" {
521 fn arc4random_buf(buffer: *mut c_void, length: usize);
522 }
523 unsafe { arc4random_buf(nonce.as_mut_ptr().cast(), nonce.len()) };
525 if nonce == [0; 32] {
526 Err(BootstrapError::InvalidEnvironment)
527 } else {
528 Ok(nonce)
529 }
530}
531
532fn mach(operation: &'static str, code: c_int) -> Result<(), BootstrapError> {
533 if code == KERN_SUCCESS {
534 Ok(())
535 } else {
536 Err(BootstrapError::Mach { operation, code })
537 }
538}
539fn spawn_result(code: c_int) -> Result<(), BootstrapError> {
540 if code == 0 {
541 Ok(())
542 } else {
543 Err(BootstrapError::Spawn(code))
544 }
545}
546fn hex(bytes: &[u8]) -> String {
547 bytes.iter().map(|byte| format!("{byte:02x}")).collect()
548}
549fn parse_nonce(encoded: &str) -> Result<[u8; 32], BootstrapError> {
550 if encoded.len() != 64 {
551 return Err(BootstrapError::InvalidEnvironment);
552 }
553 let mut nonce = [0; 32];
554 for (output, pair) in nonce.iter_mut().zip(encoded.as_bytes().chunks_exact(2)) {
555 let pair = std::str::from_utf8(pair).map_err(|_| BootstrapError::InvalidEnvironment)?;
556 *output = u8::from_str_radix(pair, 16).map_err(|_| BootstrapError::InvalidEnvironment)?;
557 }
558 Ok(nonce)
559}
560
561fn terminate_and_reap(pid: Pid) {
562 if pid <= 0 {
563 return;
564 }
565 let _ = unsafe { kill(pid, 9) };
567 let mut status = 0;
568 let _ = unsafe { waitpid(pid, &mut status, 0) };
570}
571
572const _: () = assert!(size_of::<MachMsgHeader>() == 24);
573const _: () = assert!(size_of::<MachMsgPortDescriptor>() == 12);
574const _: () = assert!(size_of::<AuditTrailer>() == 52);
575
576#[cfg(test)]
577mod tests {
578 use super::*;
579 use native_ipc_core::layout::{
580 AcknowledgementRouteSpec, Endpoint, LayoutLimits, RegionSetLayout, RegionSpec, RoleId,
581 ValidationExpectations,
582 };
583 use std::os::unix::ffi::OsStrExt;
584 use std::time::Duration;
585
586 fn topology() -> (RegionSetLayout, RoleId, RoleId) {
587 let producer = RoleId::new(1).unwrap();
588 let peer = RoleId::new(2).unwrap();
589 let specs = [
590 RegionSpec {
591 role: producer,
592 writer: Endpoint::Initiator,
593 slot_count: 1,
594 payload_bytes: 32,
595 acknowledgement_count: 1,
596 },
597 RegionSpec {
598 role: peer,
599 writer: Endpoint::Responder,
600 slot_count: 1,
601 payload_bytes: 32,
602 acknowledgement_count: 1,
603 },
604 ];
605 let routes = [
606 AcknowledgementRouteSpec {
607 owner: peer,
608 target: producer,
609 slot_index: 0,
610 cell_index: 0,
611 },
612 AcknowledgementRouteSpec {
613 owner: producer,
614 target: peer,
615 slot_index: 0,
616 cell_index: 0,
617 },
618 ];
619 let topology = RegionSetLayout::calculate(
620 [6; 32],
621 17,
622 &specs,
623 &routes,
624 LayoutLimits {
625 maximum_mapping_size: 1 << 20,
626 maximum_slot_count: 2,
627 maximum_acknowledgement_count: 2,
628 maximum_payload_bytes: 64,
629 },
630 )
631 .unwrap();
632 (topology, producer, peer)
633 }
634
635 #[test]
636 fn spawned_helper_uses_private_port_and_audit_pid() {
637 let executable = std::env::current_exe().unwrap();
638 let path = CString::new(executable.as_os_str().as_bytes()).unwrap();
639 let arguments = [
640 CString::new("--exact").unwrap(),
641 CString::new("macos::bootstrap::tests::spawned_helper_entry").unwrap(),
642 CString::new("--ignored").unwrap(),
643 CString::new("--nocapture").unwrap(),
644 ];
645 let helper = SpawnedHelper::spawn(&path, &arguments).unwrap();
646 let expected_pid = helper.pid();
647 let channel = helper.authenticate().unwrap();
648 assert_eq!(channel.peer_pid(), expected_pid);
649 }
650
651 #[test]
652 #[ignore = "spawned only by the private Mach bootstrap integration test"]
653 fn spawned_helper_entry() {
654 let _channel = ChildChannel::connect_from_environment().unwrap();
655 std::thread::sleep(Duration::from_secs(30));
656 }
657
658 #[test]
659 fn spawned_helper_imports_memory_entry_and_reads_payload() {
660 let (topology, producer, peer) = topology();
661 let layout = topology.region(producer).unwrap();
662 let mut owner = super::super::QuiescentRegion::new(layout.total_size() as usize).unwrap();
663 layout.encode_into(owner.as_bytes_mut()).unwrap();
664 let expected = ValidationExpectations {
665 schema_id: [6; 32],
666 generation: 17,
667 role: producer,
668 writer: Endpoint::Initiator,
669 maximum_mapping_size: owner.len() as u64,
670 };
671 let peer_layout = topology.region(peer).unwrap();
672 let mut peer_owner =
673 super::super::QuiescentRegion::new(peer_layout.total_size() as usize).unwrap();
674 peer_layout.encode_into(peer_owner.as_bytes_mut()).unwrap();
675 let peer_expected = ValidationExpectations {
676 schema_id: [6; 32],
677 generation: 17,
678 role: peer,
679 writer: Endpoint::Responder,
680 maximum_mapping_size: peer_owner.len() as u64,
681 };
682 let executable = std::env::current_exe().unwrap();
683 let path = CString::new(executable.as_os_str().as_bytes()).unwrap();
684 let arguments = [
685 CString::new("--exact").unwrap(),
686 CString::new("macos::bootstrap::tests::memory_entry_helper").unwrap(),
687 CString::new("--ignored").unwrap(),
688 CString::new("--nocapture").unwrap(),
689 ];
690 let helper = SpawnedHelper::spawn(&path, &arguments).unwrap();
691 let channel = helper.authenticate().unwrap();
692 let mut writer = owner
693 .transfer_local_writer(expected, topology.clone(), &channel)
694 .unwrap();
695 let peer_reader = peer_owner
696 .transfer_remote_writer(peer_expected, topology, &channel)
697 .unwrap();
698 channel.wait_ready().unwrap();
699 writer.publish(0, 1, None, b"cross-process-mach").unwrap();
700 for _ in 0..10_000 {
701 if let Ok(payload) = peer_reader.copy_payload(0, 1) {
702 assert_eq!(payload, b"child-mach-writer");
703 channel.wait().unwrap();
704 return;
705 }
706 std::thread::sleep(Duration::from_millis(1));
707 }
708 panic!("child never published payload");
709 }
710
711 #[test]
712 #[ignore = "spawned only by the memory-entry integration test"]
713 fn memory_entry_helper() {
714 let (topology, producer, peer) = topology();
715 let layout = topology.region(producer).unwrap();
716 let page = super::super::page_size().unwrap();
717 let len = super::super::page_align(layout.total_size() as usize, page).unwrap();
718 let expected = ValidationExpectations {
719 schema_id: [6; 32],
720 generation: 17,
721 role: producer,
722 writer: Endpoint::Initiator,
723 maximum_mapping_size: len as u64,
724 };
725 let peer_layout = topology.region(peer).unwrap();
726 let peer_len = super::super::page_align(peer_layout.total_size() as usize, page).unwrap();
727 let peer_expected = ValidationExpectations {
728 schema_id: [6; 32],
729 generation: 17,
730 role: peer,
731 writer: Endpoint::Responder,
732 maximum_mapping_size: peer_len as u64,
733 };
734 let channel = ChildChannel::connect_from_environment().unwrap();
735 let reader = channel
736 .receive_reader(len, expected, topology.clone())
737 .unwrap();
738 let mut peer_writer = channel
739 .receive_writer(peer_len, peer_expected, topology)
740 .unwrap();
741 channel.signal_ready().unwrap();
742 for _ in 0..10_000 {
743 if let Ok(payload) = reader.copy_payload(0, 1) {
744 assert_eq!(payload, b"cross-process-mach");
745 peer_writer
746 .publish(0, 1, None, b"child-mach-writer")
747 .unwrap();
748 return;
749 }
750 std::thread::sleep(Duration::from_millis(1));
751 }
752 panic!("parent never published payload");
753 }
754}