1use std::ffi::{OsStr, OsString};
4use std::fmt;
5use std::mem::{size_of, zeroed};
6use std::os::windows::ffi::OsStrExt;
7use std::path::Path;
8use std::ptr::NonNull;
9use std::time::{Duration, Instant};
10
11use native_ipc_core::layout::{RegionSetLayout, ValidatedRegionLayout, ValidationExpectations};
12use native_ipc_core::mapping::{
13 BindingError, ReadOnlyMapping, ReaderRegion, SoleWriterMapping, WriterRegion,
14};
15use windows_sys::Win32::Foundation::{
16 CloseHandle, DuplicateHandle, ERROR_NO_DATA, ERROR_PIPE_CONNECTED, ERROR_PIPE_LISTENING,
17 GENERIC_READ, GENERIC_WRITE, GetLastError, HANDLE, INVALID_HANDLE_VALUE, WAIT_OBJECT_0,
18 WAIT_TIMEOUT,
19};
20use windows_sys::Win32::Security::Cryptography::{
21 BCRYPT_USE_SYSTEM_PREFERRED_RNG, BCryptGenRandom,
22};
23use windows_sys::Win32::Storage::FileSystem::{
24 CreateFileW, FILE_FLAG_FIRST_PIPE_INSTANCE, OPEN_EXISTING, PIPE_ACCESS_DUPLEX, ReadFile,
25 WriteFile,
26};
27use windows_sys::Win32::System::JobObjects::{
28 AssignProcessToJobObject, CreateJobObjectW, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
29 JOBOBJECT_EXTENDED_LIMIT_INFORMATION, JobObjectExtendedLimitInformation,
30 SetInformationJobObject,
31};
32use windows_sys::Win32::System::Memory::{
33 CreateFileMappingW, FILE_MAP_READ, FILE_MAP_WRITE, MEMORY_MAPPED_VIEW_ADDRESS, MapViewOfFile,
34 PAGE_READWRITE, SEC_COMMIT, UnmapViewOfFile,
35};
36use windows_sys::Win32::System::Pipes::{
37 ConnectNamedPipe, CreateNamedPipeW, GetNamedPipeClientProcessId, GetNamedPipeServerProcessId,
38 PIPE_NOWAIT, PIPE_READMODE_MESSAGE, PIPE_REJECT_REMOTE_CLIENTS, PIPE_TYPE_MESSAGE,
39 SetNamedPipeHandleState,
40};
41use windows_sys::Win32::System::SystemInformation::{GetSystemInfo, SYSTEM_INFO};
42use windows_sys::Win32::System::Threading::{
43 CREATE_SUSPENDED, CREATE_UNICODE_ENVIRONMENT, CreateProcessW, GetCurrentProcess,
44 GetCurrentProcessId, GetExitCodeProcess, PROCESS_INFORMATION, ResumeThread, STARTUPINFOW,
45 TerminateProcess, WaitForSingleObject,
46};
47
48use crate::BackendStatus;
49use crate::protocol::{
50 CONTROL_FRAME_LEN, ManifestEntry, PeerAccess, TransferManifest, TransferProvenance,
51 mint_channel_id,
52};
53
54#[derive(Debug)]
56pub enum WindowsError {
57 Os {
59 operation: &'static str,
61 code: u32,
63 },
64 InvalidSize(usize),
66 WrongPeer,
68 InvalidHandle,
70 Layout(native_ipc_core::layout::LayoutError),
72 Binding(BindingError),
74 InvalidBootstrap,
76 CapabilityAlreadyTransferred,
78 TimedOut(&'static str),
80 ChildExit(u32),
82 ForeignPending,
84}
85
86impl fmt::Display for WindowsError {
87 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
88 write!(formatter, "Windows transport failed: {self:?}")
89 }
90}
91impl std::error::Error for WindowsError {}
92impl From<native_ipc_core::layout::LayoutError> for WindowsError {
93 fn from(value: native_ipc_core::layout::LayoutError) -> Self {
94 Self::Layout(value)
95 }
96}
97impl From<BindingError> for WindowsError {
98 fn from(value: BindingError) -> Self {
99 Self::Binding(value)
100 }
101}
102
103pub fn session_nonce() -> Result<[u8; 32], WindowsError> {
105 let mut nonce = [0_u8; 32];
106 let status = unsafe {
108 BCryptGenRandom(
109 std::ptr::null_mut(),
110 nonce.as_mut_ptr(),
111 nonce.len() as u32,
112 BCRYPT_USE_SYSTEM_PREFERRED_RNG,
113 )
114 };
115 if status < 0 || nonce == [0; 32] {
116 Err(last_os("BCryptGenRandom"))
117 } else {
118 Ok(nonce)
119 }
120}
121
122pub unsafe fn authenticate_pipe_client(
128 pipe: HANDLE,
129 expected_pid: u32,
130) -> Result<(), WindowsError> {
131 let mut actual = 0;
132 if unsafe { GetNamedPipeClientProcessId(pipe, &mut actual) } == 0 {
134 return Err(last_os("GetNamedPipeClientProcessId"));
135 }
136 if actual == expected_pid {
137 Ok(())
138 } else {
139 Err(WindowsError::WrongPeer)
140 }
141}
142
143pub unsafe fn authenticate_pipe_server(
149 pipe: HANDLE,
150 expected_pid: u32,
151) -> Result<(), WindowsError> {
152 let mut actual = 0;
153 if unsafe { GetNamedPipeServerProcessId(pipe, &mut actual) } == 0 {
155 return Err(last_os("GetNamedPipeServerProcessId"));
156 }
157 if actual == expected_pid {
158 Ok(())
159 } else {
160 Err(WindowsError::WrongPeer)
161 }
162}
163
164pub struct QuiescentRegion {
166 section: OwnedHandle,
167 view: View,
168 logical_len: usize,
169}
170
171impl QuiescentRegion {
172 pub fn new(logical_len: usize) -> Result<Self, WindowsError> {
174 let len = page_align(logical_len)?;
175 let size = len as u64;
176 let section = unsafe {
178 CreateFileMappingW(
179 INVALID_HANDLE_VALUE,
180 std::ptr::null(),
181 PAGE_READWRITE | SEC_COMMIT,
182 (size >> 32) as u32,
183 size as u32,
184 std::ptr::null(),
185 )
186 };
187 let section = OwnedHandle::new(section)?;
188 let view = View::map(section.0, len, FILE_MAP_WRITE)?;
189 unsafe { std::slice::from_raw_parts_mut(view.base.as_ptr(), len) }.fill(0);
191 Ok(Self {
192 section,
193 view,
194 logical_len,
195 })
196 }
197 pub const fn len(&self) -> usize {
199 self.view.len
200 }
201 pub const fn is_empty(&self) -> bool {
203 false
204 }
205 pub const fn logical_len(&self) -> usize {
207 self.logical_len
208 }
209 pub fn as_bytes(&self) -> &[u8] {
211 unsafe { std::slice::from_raw_parts(self.view.base.as_ptr(), self.view.len) }
213 }
214 pub fn as_bytes_mut(&mut self) -> &mut [u8] {
216 unsafe { std::slice::from_raw_parts_mut(self.view.base.as_ptr(), self.view.len) }
218 }
219
220 pub fn prepare_local_writer(
222 self,
223 expected: ValidationExpectations,
224 topology: RegionSetLayout,
225 ) -> Result<PreparedLocalWriter, WindowsError> {
226 let layout =
228 unsafe { ValidatedRegionLayout::validate(self.as_bytes(), expected, &topology) }?;
229 let len = self.view.len;
230 let entry = ManifestEntry::validated(expected, len, PeerAccess::ReadOnly)
231 .ok_or(WindowsError::InvalidBootstrap)?;
232 Ok(PreparedLocalWriter {
233 section: self.section,
234 runtime: WriterRegion::new(WindowsWriterMapping { view: self.view }, layout, topology)?,
235 entry,
236 len,
237 reader_duplicated: false,
238 })
239 }
240
241 pub fn prepare_remote_writer(
243 self,
244 expected: ValidationExpectations,
245 topology: RegionSetLayout,
246 ) -> Result<PreparedRemoteWriter, WindowsError> {
247 let layout =
249 unsafe { ValidatedRegionLayout::validate(self.as_bytes(), expected, &topology) }?;
250 let len = self.view.len;
251 drop(self.view);
252 let view = View::map(self.section.0, len, FILE_MAP_READ)?;
253 let entry = ManifestEntry::validated(expected, len, PeerAccess::SoleWriter)
254 .ok_or(WindowsError::InvalidBootstrap)?;
255 Ok(PreparedRemoteWriter {
256 section: self.section,
257 runtime: ReaderRegion::new(WindowsReaderMapping { view }, layout, topology)?,
258 entry,
259 len,
260 writer_duplicated: false,
261 })
262 }
263}
264
265#[derive(Clone, Copy, Debug, Eq, PartialEq)]
267pub struct RemoteHandle(pub usize);
268
269pub struct PreparedLocalWriter {
278 section: OwnedHandle,
279 runtime: WriterRegion<WindowsWriterMapping>,
280 entry: ManifestEntry,
281 len: usize,
282 reader_duplicated: bool,
283}
284impl PreparedLocalWriter {
285 unsafe fn duplicate_reader_to(
291 &mut self,
292 target_process: HANDLE,
293 ) -> Result<RemoteHandle, WindowsError> {
294 if self.reader_duplicated {
295 return Err(WindowsError::CapabilityAlreadyTransferred);
296 }
297 let handle = duplicate_to(self.section.0, target_process, FILE_MAP_READ)?;
298 self.reader_duplicated = true;
299 Ok(handle)
300 }
301}
302
303pub struct PreparedRemoteWriter {
305 section: OwnedHandle,
306 runtime: ReaderRegion<WindowsReaderMapping>,
307 entry: ManifestEntry,
308 len: usize,
309 writer_duplicated: bool,
310}
311impl PreparedRemoteWriter {
312 unsafe fn duplicate_writer_to(
318 &mut self,
319 target_process: HANDLE,
320 ) -> Result<RemoteHandle, WindowsError> {
321 if self.writer_duplicated {
322 return Err(WindowsError::CapabilityAlreadyTransferred);
323 }
324 let handle = duplicate_to(self.section.0, target_process, FILE_MAP_WRITE)?;
325 self.writer_duplicated = true;
326 Ok(handle)
327 }
328}
329
330pub struct PendingImportedReader {
339 runtime: ReaderRegion<WindowsReaderMapping>,
340 entry: ManifestEntry,
341 provenance: TransferProvenance,
342}
343
344pub struct PendingImportedWriter {
346 runtime: WriterRegion<WindowsWriterMapping>,
347 entry: ManifestEntry,
348 provenance: TransferProvenance,
349}
350
351pub struct ChildJob(OwnedHandle);
353impl ChildJob {
354 pub fn new() -> Result<Self, WindowsError> {
356 let handle = unsafe { CreateJobObjectW(std::ptr::null(), std::ptr::null()) };
358 let handle = OwnedHandle::new(handle)?;
359 let mut information: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = unsafe { zeroed() };
360 information.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
361 if unsafe {
363 SetInformationJobObject(
364 handle.0,
365 JobObjectExtendedLimitInformation,
366 (&information as *const JOBOBJECT_EXTENDED_LIMIT_INFORMATION).cast(),
367 size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() as u32,
368 )
369 } == 0
370 {
371 return Err(last_os("SetInformationJobObject"));
372 }
373 Ok(Self(handle))
374 }
375 pub unsafe fn assign_suspended(&self, process: HANDLE) -> Result<(), WindowsError> {
381 if unsafe { AssignProcessToJobObject(self.0.0, process) } == 0 {
383 Err(last_os("AssignProcessToJobObject"))
384 } else {
385 Ok(())
386 }
387 }
388}
389
390const PIPE_ENV: &str = "NATIVE_IPC_WINDOWS_PIPE";
391const NONCE_ENV: &str = "NATIVE_IPC_WINDOWS_NONCE";
392const PARENT_ENV: &str = "NATIVE_IPC_PARENT_PID";
393const BOOTSTRAP_MAGIC: [u8; 8] = *b"NIPCWIN1";
394const AUTH_MAGIC: [u8; 8] = *b"NIPCAUT1";
395const READY_MAGIC: [u8; 8] = *b"NIPCRDY1";
396const COMMIT_MAGIC: [u8; 8] = *b"NIPCCMT1";
397const CAPABILITY_MAGIC: [u8; 8] = *b"NIPCCAP1";
398#[cfg(not(test))]
399const WAIT_MS: u32 = 10_000;
400#[cfg(test)]
401const WAIT_MS: u32 = 1_000;
402
403#[repr(C)]
404#[derive(Clone, Copy)]
405struct BootstrapFrame {
406 magic: [u8; 8],
407 nonce: [u8; 32],
408 parent_pid: u32,
409 child_pid: u32,
410}
411
412const CAPABILITY_FRAME_LEN: usize = 40 + CONTROL_FRAME_LEN;
413
414fn encode_capability_frame(
415 reader: RemoteHandle,
416 reader_len: usize,
417 writer: RemoteHandle,
418 writer_len: usize,
419 transcript: &[u8; CONTROL_FRAME_LEN],
420) -> Result<[u8; CAPABILITY_FRAME_LEN], WindowsError> {
421 let mut frame = [0_u8; CAPABILITY_FRAME_LEN];
422 frame[..8].copy_from_slice(&CAPABILITY_MAGIC);
423 frame[8..16].copy_from_slice(
424 &u64::try_from(reader.0)
425 .map_err(|_| WindowsError::InvalidBootstrap)?
426 .to_le_bytes(),
427 );
428 frame[16..24].copy_from_slice(
429 &u64::try_from(writer.0)
430 .map_err(|_| WindowsError::InvalidBootstrap)?
431 .to_le_bytes(),
432 );
433 frame[24..32].copy_from_slice(
434 &u64::try_from(reader_len)
435 .map_err(|_| WindowsError::InvalidBootstrap)?
436 .to_le_bytes(),
437 );
438 frame[32..40].copy_from_slice(
439 &u64::try_from(writer_len)
440 .map_err(|_| WindowsError::InvalidBootstrap)?
441 .to_le_bytes(),
442 );
443 frame[40..].copy_from_slice(transcript);
444 Ok(frame)
445}
446
447pub struct ChildSession {
449 pipe: OwnedHandle,
450 process: OwnedHandle,
451 _job: ChildJob,
452 pid: u32,
453 nonce: [u8; 32],
454 reaped: bool,
455 next_transfer_id: u64,
456 pending_manifest: Option<TransferManifest>,
457}
458
459impl ChildSession {
460 pub fn spawn(path: &Path, arguments: &[OsString]) -> Result<Self, WindowsError> {
462 let nonce = session_nonce()?;
463 let name = format!(r"\\.\pipe\native-ipc-{}", hex(&nonce));
464 let pipe_name = wide_null(OsStr::new(&name));
465 let pipe = unsafe {
467 CreateNamedPipeW(
468 pipe_name.as_ptr(),
469 PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE,
470 PIPE_TYPE_MESSAGE
471 | PIPE_READMODE_MESSAGE
472 | PIPE_NOWAIT
473 | PIPE_REJECT_REMOTE_CLIENTS,
474 1,
475 4096,
476 4096,
477 WAIT_MS,
478 std::ptr::null(),
479 )
480 };
481 let pipe = OwnedHandle::new(pipe)?;
482 let job = ChildJob::new()?;
483
484 let application = wide_null(path.as_os_str());
485 let mut command = command_line(path.as_os_str(), arguments);
486 let parent_pid = unsafe { GetCurrentProcessId() };
487 let environment = environment_block(&[
488 (PIPE_ENV, name),
489 (NONCE_ENV, hex(&nonce)),
490 (PARENT_ENV, parent_pid.to_string()),
491 ]);
492 let mut startup: STARTUPINFOW = unsafe { zeroed() };
493 startup.cb = size_of::<STARTUPINFOW>() as u32;
494 let mut information: PROCESS_INFORMATION = unsafe { zeroed() };
495 if unsafe {
497 CreateProcessW(
498 application.as_ptr(),
499 command.as_mut_ptr(),
500 std::ptr::null(),
501 std::ptr::null(),
502 0,
503 CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT,
504 environment.as_ptr().cast(),
505 std::ptr::null(),
506 &startup,
507 &mut information,
508 )
509 } == 0
510 {
511 return Err(last_os("CreateProcessW"));
512 }
513 let process = OwnedHandle::new(information.hProcess)?;
514 let thread = OwnedHandle::new(information.hThread)?;
515 if let Err(error) = unsafe { job.assign_suspended(process.0) } {
517 let _ = unsafe { TerminateProcess(process.0, 127) };
519 return Err(error);
520 }
521 if unsafe { ResumeThread(thread.0) } == u32::MAX {
523 let error = last_os("ResumeThread");
524 let _ = unsafe { TerminateProcess(process.0, 127) };
525 return Err(error);
526 }
527 drop(thread);
528 connect_pipe(pipe.0, process.0)?;
529 unsafe { authenticate_pipe_client(pipe.0, information.dwProcessId)? };
531 let hello = BootstrapFrame {
532 magic: BOOTSTRAP_MAGIC,
533 nonce,
534 parent_pid,
535 child_pid: information.dwProcessId,
536 };
537 write_frame(pipe.0, &hello)?;
538 let ready = read_frame(pipe.0)?;
539 if ready.magic != AUTH_MAGIC
540 || ready.nonce != nonce
541 || ready.parent_pid != parent_pid
542 || ready.child_pid != information.dwProcessId
543 {
544 let _ = unsafe { TerminateProcess(process.0, 127) };
545 return Err(WindowsError::InvalidBootstrap);
546 }
547 Ok(Self {
548 pipe,
549 process,
550 _job: job,
551 pid: information.dwProcessId,
552 nonce,
553 reaped: false,
554 next_transfer_id: 1,
555 pending_manifest: None,
556 })
557 }
558
559 pub const fn process_handle(&self) -> HANDLE {
561 self.process.0
562 }
563 pub const fn pid(&self) -> u32 {
565 self.pid
566 }
567 fn send_capabilities(
569 &mut self,
570 reader_handle: RemoteHandle,
571 reader: &PreparedLocalWriter,
572 writer_handle: RemoteHandle,
573 remote_writer: &PreparedRemoteWriter,
574 ) -> Result<(), WindowsError> {
575 if self.pending_manifest.is_some() {
576 return Err(WindowsError::InvalidBootstrap);
577 }
578 let manifest = TransferManifest::new(
579 self.nonce,
580 unsafe { GetCurrentProcessId() },
581 self.pid,
582 self.next_transfer_id,
583 vec![reader.entry, remote_writer.entry],
584 )
585 .ok_or(WindowsError::InvalidBootstrap)?;
586 let frame = encode_capability_frame(
587 reader_handle,
588 reader.len,
589 writer_handle,
590 remote_writer.len,
591 &manifest.encode(CAPABILITY_MAGIC),
592 )?;
593 write_pod(self.pipe.0, &frame)?;
594 self.pending_manifest = Some(manifest);
595 Ok(())
596 }
597 pub fn commit_transfers(
622 &mut self,
623 writer: PreparedLocalWriter,
624 reader: PreparedRemoteWriter,
625 ) -> Result<
626 (
627 WriterRegion<WindowsWriterMapping>,
628 ReaderRegion<WindowsReaderMapping>,
629 ),
630 WindowsError,
631 > {
632 let result = self.commit_transfers_inner(writer, reader);
633 if result.is_err() {
634 self.abort_child();
635 }
636 result
637 }
638
639 fn commit_transfers_inner(
640 &mut self,
641 mut writer: PreparedLocalWriter,
642 mut reader: PreparedRemoteWriter,
643 ) -> Result<
644 (
645 WriterRegion<WindowsWriterMapping>,
646 ReaderRegion<WindowsReaderMapping>,
647 ),
648 WindowsError,
649 > {
650 let reader_handle = unsafe { writer.duplicate_reader_to(self.process.0)? };
652 let writer_handle = unsafe { reader.duplicate_writer_to(self.process.0)? };
654 self.send_capabilities(reader_handle, &writer, writer_handle, &reader)?;
655 let manifest = self
656 .pending_manifest
657 .as_ref()
658 .ok_or(WindowsError::InvalidBootstrap)?;
659 let ready: [u8; CONTROL_FRAME_LEN] = read_pod(self.pipe.0)?;
660 if !manifest.matches_frame(READY_MAGIC, &ready) {
661 return Err(WindowsError::InvalidBootstrap);
662 }
663 write_pod(self.pipe.0, &manifest.encode(COMMIT_MAGIC))?;
664 drop(writer.section);
665 drop(reader.section);
666 self.pending_manifest = None;
667 self.next_transfer_id = self
668 .next_transfer_id
669 .checked_add(1)
670 .ok_or(WindowsError::InvalidBootstrap)?;
671 Ok((writer.runtime, reader.runtime))
672 }
673
674 fn abort_child(&mut self) {
675 if !self.reaped {
676 let _ = unsafe { TerminateProcess(self.process.0, 127) };
678 let _ = unsafe { WaitForSingleObject(self.process.0, WAIT_MS) };
680 self.reaped = true;
681 }
682 }
683 pub fn wait(mut self) -> Result<(), WindowsError> {
685 match unsafe { WaitForSingleObject(self.process.0, WAIT_MS) } {
687 WAIT_OBJECT_0 => {
688 let mut code = 0;
689 if unsafe { GetExitCodeProcess(self.process.0, &mut code) } == 0 {
691 return Err(last_os("GetExitCodeProcess"));
692 }
693 if code != 0 {
694 return Err(WindowsError::ChildExit(code));
695 }
696 }
697 WAIT_TIMEOUT => return Err(WindowsError::TimedOut("helper exit")),
698 _ => return Err(last_os("WaitForSingleObject")),
699 }
700 self.reaped = true;
701 Ok(())
702 }
703}
704
705impl Drop for ChildSession {
706 fn drop(&mut self) {
707 if !self.reaped {
708 let _ = unsafe { TerminateProcess(self.process.0, 127) };
710 let _ = unsafe { WaitForSingleObject(self.process.0, WAIT_MS) };
711 }
712 }
713}
714
715pub fn connect_spawned_helper() -> Result<ChildChannel, WindowsError> {
717 let name = std::env::var_os(PIPE_ENV).ok_or(WindowsError::InvalidBootstrap)?;
718 let nonce =
719 parse_nonce(&std::env::var(NONCE_ENV).map_err(|_| WindowsError::InvalidBootstrap)?)?;
720 let parent_pid = std::env::var(PARENT_ENV)
721 .map_err(|_| WindowsError::InvalidBootstrap)?
722 .parse::<u32>()
723 .map_err(|_| WindowsError::InvalidBootstrap)?;
724 let name = wide_null(&name);
725 let pipe = unsafe {
727 CreateFileW(
728 name.as_ptr(),
729 GENERIC_READ | GENERIC_WRITE,
730 0,
731 std::ptr::null(),
732 OPEN_EXISTING,
733 0,
734 std::ptr::null_mut(),
735 )
736 };
737 let pipe = OwnedHandle::new(pipe)?;
738 let mode = PIPE_READMODE_MESSAGE | PIPE_NOWAIT;
739 if unsafe { SetNamedPipeHandleState(pipe.0, &mode, std::ptr::null(), std::ptr::null()) } == 0 {
741 return Err(last_os("SetNamedPipeHandleState"));
742 }
743 unsafe { authenticate_pipe_server(pipe.0, parent_pid)? };
745 let hello = read_frame(pipe.0)?;
746 let child_pid = unsafe { GetCurrentProcessId() };
747 if hello.magic != BOOTSTRAP_MAGIC
748 || hello.nonce != nonce
749 || hello.parent_pid != parent_pid
750 || hello.child_pid != child_pid
751 {
752 return Err(WindowsError::InvalidBootstrap);
753 }
754 write_frame(
755 pipe.0,
756 &BootstrapFrame {
757 magic: AUTH_MAGIC,
758 ..hello
759 },
760 )?;
761 Ok(ChildChannel {
762 pipe,
763 parent_pid,
764 nonce,
765 channel_id: mint_channel_id(),
766 next_transfer_id: 1,
767 pending_transcript: None,
768 poisoned: false,
769 })
770}
771
772pub struct ChildChannel {
774 pipe: OwnedHandle,
775 parent_pid: u32,
776 nonce: [u8; 32],
777 channel_id: u64,
778 next_transfer_id: u64,
779 pending_transcript: Option<[u8; CONTROL_FRAME_LEN]>,
780 poisoned: bool,
781}
782impl ChildChannel {
783 pub const fn parent_pid(&self) -> u32 {
785 self.parent_pid
786 }
787 pub const fn pipe_handle(&self) -> HANDLE {
789 self.pipe.0
790 }
791 pub fn receive_capabilities(
802 &mut self,
803 ) -> Result<(RemoteHandle, usize, RemoteHandle, usize), WindowsError> {
804 if self.poisoned || self.pending_transcript.is_some() {
805 return Err(WindowsError::InvalidBootstrap);
806 }
807 let frame: [u8; CAPABILITY_FRAME_LEN] = read_pod(self.pipe.0)?;
808 if frame[..8] != CAPABILITY_MAGIC {
809 return Err(WindowsError::InvalidBootstrap);
810 }
811 let reader_handle = usize::try_from(u64::from_le_bytes(
812 frame[8..16].try_into().expect("fixed range"),
813 ))
814 .map_err(|_| WindowsError::InvalidBootstrap)?;
815 let writer_handle = usize::try_from(u64::from_le_bytes(
816 frame[16..24].try_into().expect("fixed range"),
817 ))
818 .map_err(|_| WindowsError::InvalidBootstrap)?;
819 let reader_len = usize::try_from(u64::from_le_bytes(
820 frame[24..32].try_into().expect("fixed range"),
821 ))
822 .map_err(|_| WindowsError::InvalidBootstrap)?;
823 let writer_len = usize::try_from(u64::from_le_bytes(
824 frame[32..40].try_into().expect("fixed range"),
825 ))
826 .map_err(|_| WindowsError::InvalidBootstrap)?;
827 if reader_handle == 0 || writer_handle == 0 || reader_len == 0 || writer_len == 0 {
828 return Err(WindowsError::InvalidBootstrap);
829 }
830 let mut transcript = [0; CONTROL_FRAME_LEN];
831 transcript.copy_from_slice(&frame[40..]);
832 self.pending_transcript = Some(transcript);
833 Ok((
834 RemoteHandle(reader_handle),
835 reader_len,
836 RemoteHandle(writer_handle),
837 writer_len,
838 ))
839 }
840 pub unsafe fn import_reader(
852 &self,
853 handle: usize,
854 len: usize,
855 expected: ValidationExpectations,
856 topology: RegionSetLayout,
857 ) -> Result<PendingImportedReader, WindowsError> {
858 let section = OwnedHandle::new(handle as HANDLE)?;
859 let view = View::map(section.0, len, FILE_MAP_READ)?;
860 let bytes = unsafe { std::slice::from_raw_parts(view.base.as_ptr(), len) };
862 let layout = unsafe { ValidatedRegionLayout::validate(bytes, expected, &topology) }?;
863 let entry = ManifestEntry::validated(expected, len, PeerAccess::ReadOnly)
864 .ok_or(WindowsError::InvalidBootstrap)?;
865 drop(section);
866 Ok(PendingImportedReader {
867 runtime: ReaderRegion::new(WindowsReaderMapping { view }, layout, topology)?,
868 entry,
869 provenance: self.pending_provenance(),
870 })
871 }
872
873 pub unsafe fn import_writer(
881 &self,
882 handle: usize,
883 len: usize,
884 expected: ValidationExpectations,
885 topology: RegionSetLayout,
886 ) -> Result<PendingImportedWriter, WindowsError> {
887 let section = OwnedHandle::new(handle as HANDLE)?;
888 let view = View::map(section.0, len, FILE_MAP_WRITE)?;
889 let bytes = unsafe { std::slice::from_raw_parts(view.base.as_ptr(), len) };
891 let layout = unsafe { ValidatedRegionLayout::validate(bytes, expected, &topology) }?;
892 let entry = ManifestEntry::validated(expected, len, PeerAccess::SoleWriter)
893 .ok_or(WindowsError::InvalidBootstrap)?;
894 drop(section);
895 Ok(PendingImportedWriter {
896 runtime: WriterRegion::new(WindowsWriterMapping { view }, layout, topology)?,
897 entry,
898 provenance: self.pending_provenance(),
899 })
900 }
901
902 const fn pending_provenance(&self) -> TransferProvenance {
904 TransferProvenance::new(self.channel_id, self.next_transfer_id)
905 }
906
907 pub fn commit_imports(
917 &mut self,
918 reader: PendingImportedReader,
919 writer: PendingImportedWriter,
920 ) -> Result<
921 (
922 ReaderRegion<WindowsReaderMapping>,
923 WriterRegion<WindowsWriterMapping>,
924 ),
925 WindowsError,
926 > {
927 if self.poisoned {
928 return Err(WindowsError::InvalidBootstrap);
929 }
930 let expected = self.pending_provenance();
931 if reader.provenance != expected || writer.provenance != expected {
932 self.poisoned = true;
933 return Err(WindowsError::ForeignPending);
934 }
935 let manifest = TransferManifest::new(
936 self.nonce,
937 self.parent_pid,
938 unsafe { GetCurrentProcessId() },
939 self.next_transfer_id,
940 vec![reader.entry, writer.entry],
941 )
942 .ok_or(WindowsError::InvalidBootstrap)?;
943 let transcript = self
944 .pending_transcript
945 .as_ref()
946 .ok_or(WindowsError::InvalidBootstrap)?;
947 if !manifest.matches_frame(CAPABILITY_MAGIC, transcript) {
948 return Err(WindowsError::InvalidBootstrap);
949 }
950 write_pod(self.pipe.0, &manifest.encode(READY_MAGIC))?;
951 let commit: [u8; CONTROL_FRAME_LEN] = read_pod(self.pipe.0)?;
952 if !manifest.matches_frame(COMMIT_MAGIC, &commit) {
953 return Err(WindowsError::InvalidBootstrap);
954 }
955 self.pending_transcript = None;
956 self.next_transfer_id = self
957 .next_transfer_id
958 .checked_add(1)
959 .ok_or(WindowsError::InvalidBootstrap)?;
960 Ok((reader.runtime, writer.runtime))
961 }
962}
963
964pub struct WindowsWriterMapping {
966 view: View,
967}
968unsafe impl SoleWriterMapping for WindowsWriterMapping {
970 fn base(&self) -> NonNull<u8> {
971 self.view.base
972 }
973 fn len(&self) -> usize {
974 self.view.len
975 }
976}
977pub struct WindowsReaderMapping {
979 view: View,
980}
981unsafe impl ReadOnlyMapping for WindowsReaderMapping {
983 fn base(&self) -> NonNull<u8> {
984 self.view.base
985 }
986 fn len(&self) -> usize {
987 self.view.len
988 }
989}
990
991struct OwnedHandle(HANDLE);
992impl OwnedHandle {
993 fn new(handle: HANDLE) -> Result<Self, WindowsError> {
994 if handle.is_null() || handle == INVALID_HANDLE_VALUE {
995 Err(WindowsError::InvalidHandle)
996 } else {
997 Ok(Self(handle))
998 }
999 }
1000}
1001impl Drop for OwnedHandle {
1002 fn drop(&mut self) {
1003 let _ = unsafe { CloseHandle(self.0) };
1005 }
1006}
1007
1008struct View {
1009 base: NonNull<u8>,
1010 len: usize,
1011}
1012impl View {
1013 fn map(section: HANDLE, len: usize, access: u32) -> Result<Self, WindowsError> {
1014 let address = unsafe { MapViewOfFile(section, access, 0, 0, len) };
1016 let base = NonNull::new(address.Value.cast()).ok_or_else(|| last_os("MapViewOfFile"))?;
1017 Ok(Self { base, len })
1018 }
1019}
1020impl Drop for View {
1021 fn drop(&mut self) {
1022 let _ = unsafe {
1024 UnmapViewOfFile(MEMORY_MAPPED_VIEW_ADDRESS {
1025 Value: self.base.as_ptr().cast(),
1026 })
1027 };
1028 }
1029}
1030
1031fn duplicate_to(source: HANDLE, target: HANDLE, access: u32) -> Result<RemoteHandle, WindowsError> {
1032 let mut remote: HANDLE = std::ptr::null_mut();
1033 if unsafe {
1035 DuplicateHandle(
1036 GetCurrentProcess(),
1037 source,
1038 target,
1039 &mut remote,
1040 access,
1041 0,
1042 0,
1043 )
1044 } == 0
1045 {
1046 return Err(last_os("DuplicateHandle"));
1047 }
1048 if remote.is_null() {
1049 Err(WindowsError::InvalidHandle)
1050 } else {
1051 Ok(RemoteHandle(remote as usize))
1052 }
1053}
1054
1055fn page_align(size: usize) -> Result<usize, WindowsError> {
1056 if size == 0 {
1057 return Err(WindowsError::InvalidSize(size));
1058 }
1059 let mut information: SYSTEM_INFO = unsafe { zeroed() };
1060 unsafe { GetSystemInfo(&mut information) };
1062 let page = information.dwPageSize as usize;
1063 if page == 0 || !page.is_power_of_two() {
1064 return Err(WindowsError::InvalidSize(size));
1065 }
1066 size.checked_add(page - 1)
1067 .map(|value| value & !(page - 1))
1068 .filter(|value| *value <= isize::MAX as usize)
1069 .ok_or(WindowsError::InvalidSize(size))
1070}
1071
1072fn wide_null(value: &OsStr) -> Vec<u16> {
1073 value.encode_wide().chain(std::iter::once(0)).collect()
1074}
1075
1076fn quote_argument(value: &OsStr, output: &mut Vec<u16>) {
1077 let units: Vec<u16> = value.encode_wide().collect();
1078 let needs_quotes = units.is_empty()
1079 || units
1080 .iter()
1081 .any(|unit| *unit == b' ' as u16 || *unit == b'\t' as u16);
1082 if !needs_quotes {
1083 output.extend(units);
1084 return;
1085 }
1086 output.push(b'"' as u16);
1087 let mut slashes = 0;
1088 for unit in units {
1089 if unit == b'\\' as u16 {
1090 slashes += 1;
1091 } else if unit == b'"' as u16 {
1092 output.extend(std::iter::repeat_n(b'\\' as u16, slashes * 2 + 1));
1093 output.push(unit);
1094 slashes = 0;
1095 } else {
1096 output.extend(std::iter::repeat_n(b'\\' as u16, slashes));
1097 output.push(unit);
1098 slashes = 0;
1099 }
1100 }
1101 output.extend(std::iter::repeat_n(b'\\' as u16, slashes * 2));
1102 output.push(b'"' as u16);
1103}
1104
1105fn command_line(path: &OsStr, arguments: &[OsString]) -> Vec<u16> {
1106 let mut result = Vec::new();
1107 quote_argument(path, &mut result);
1108 for argument in arguments {
1109 result.push(b' ' as u16);
1110 quote_argument(argument, &mut result);
1111 }
1112 result.push(0);
1113 result
1114}
1115
1116fn environment_block(overrides: &[(&str, String)]) -> Vec<u16> {
1117 let mut values: Vec<(OsString, OsString)> = std::env::vars_os()
1118 .filter(|(key, _)| {
1119 !overrides
1120 .iter()
1121 .any(|(name, _)| key.to_string_lossy().eq_ignore_ascii_case(name))
1122 })
1123 .collect();
1124 values.extend(
1125 overrides
1126 .iter()
1127 .map(|(key, value)| (OsString::from(key), OsString::from(value))),
1128 );
1129 values.sort_by(|left, right| {
1130 left.0
1131 .to_string_lossy()
1132 .to_ascii_lowercase()
1133 .cmp(&right.0.to_string_lossy().to_ascii_lowercase())
1134 });
1135 let mut block = Vec::new();
1136 for (key, value) in values {
1137 block.extend(key.encode_wide());
1138 block.push(b'=' as u16);
1139 block.extend(value.encode_wide());
1140 block.push(0);
1141 }
1142 block.push(0);
1143 block
1144}
1145
1146fn pod_bytes<T>(value: &T) -> &[u8] {
1147 unsafe { std::slice::from_raw_parts((value as *const T).cast(), size_of::<T>()) }
1149}
1150
1151fn write_frame(pipe: HANDLE, frame: &BootstrapFrame) -> Result<(), WindowsError> {
1152 write_pod(pipe, frame)
1153}
1154
1155fn write_pod<T>(pipe: HANDLE, value: &T) -> Result<(), WindowsError> {
1156 let bytes = pod_bytes(value);
1157 let deadline = Instant::now() + Duration::from_millis(WAIT_MS.into());
1158 loop {
1159 let mut written = 0;
1160 if unsafe {
1162 WriteFile(
1163 pipe,
1164 bytes.as_ptr(),
1165 bytes.len() as u32,
1166 &mut written,
1167 std::ptr::null_mut(),
1168 )
1169 } != 0
1170 {
1171 return if written as usize == bytes.len() {
1172 Ok(())
1173 } else {
1174 Err(WindowsError::InvalidBootstrap)
1175 };
1176 }
1177 let code = unsafe { GetLastError() };
1178 if code != ERROR_NO_DATA && code != ERROR_PIPE_LISTENING {
1179 return Err(WindowsError::Os {
1180 operation: "WriteFile",
1181 code,
1182 });
1183 }
1184 wait_retry(deadline, "pipe write")?;
1185 }
1186}
1187
1188fn read_frame(pipe: HANDLE) -> Result<BootstrapFrame, WindowsError> {
1189 read_pod(pipe)
1190}
1191
1192fn read_pod<T>(pipe: HANDLE) -> Result<T, WindowsError> {
1193 let deadline = Instant::now() + Duration::from_millis(WAIT_MS.into());
1194 loop {
1195 let mut value: T = unsafe { zeroed() };
1196 let mut read = 0;
1197 if unsafe {
1199 ReadFile(
1200 pipe,
1201 (&mut value as *mut T).cast(),
1202 size_of::<T>() as u32,
1203 &mut read,
1204 std::ptr::null_mut(),
1205 )
1206 } != 0
1207 {
1208 return if read as usize == size_of::<T>() {
1209 Ok(value)
1210 } else {
1211 Err(WindowsError::InvalidBootstrap)
1212 };
1213 }
1214 let code = unsafe { GetLastError() };
1215 if code != ERROR_NO_DATA && code != ERROR_PIPE_LISTENING {
1216 return Err(WindowsError::Os {
1217 operation: "ReadFile",
1218 code,
1219 });
1220 }
1221 wait_retry(deadline, "pipe read")?;
1222 }
1223}
1224
1225fn connect_pipe(pipe: HANDLE, process: HANDLE) -> Result<(), WindowsError> {
1226 let deadline = Instant::now() + Duration::from_millis(WAIT_MS.into());
1227 loop {
1228 if unsafe { ConnectNamedPipe(pipe, std::ptr::null_mut()) } != 0 {
1230 return Ok(());
1231 }
1232 let code = unsafe { GetLastError() };
1233 if code == ERROR_PIPE_CONNECTED {
1234 return Ok(());
1235 }
1236 if code != ERROR_PIPE_LISTENING && code != ERROR_NO_DATA {
1237 return Err(WindowsError::Os {
1238 operation: "ConnectNamedPipe",
1239 code,
1240 });
1241 }
1242 if unsafe { WaitForSingleObject(process, 0) } == WAIT_OBJECT_0 {
1244 let mut exit = 0;
1245 if unsafe { GetExitCodeProcess(process, &mut exit) } != 0 {
1247 return Err(WindowsError::ChildExit(exit));
1248 }
1249 return Err(last_os("GetExitCodeProcess"));
1250 }
1251 wait_retry(deadline, "pipe connect")?;
1252 }
1253}
1254
1255fn wait_retry(deadline: Instant, operation: &'static str) -> Result<(), WindowsError> {
1256 if Instant::now() >= deadline {
1257 Err(WindowsError::TimedOut(operation))
1258 } else {
1259 std::thread::sleep(Duration::from_millis(1));
1260 Ok(())
1261 }
1262}
1263
1264fn hex(bytes: &[u8; 32]) -> String {
1265 let mut output = String::with_capacity(64);
1266 for byte in bytes {
1267 use std::fmt::Write as _;
1268 write!(&mut output, "{byte:02x}").expect("String writes are infallible");
1269 }
1270 output
1271}
1272
1273fn parse_nonce(value: &str) -> Result<[u8; 32], WindowsError> {
1274 if value.len() != 64 {
1275 return Err(WindowsError::InvalidBootstrap);
1276 }
1277 let mut nonce = [0; 32];
1278 for (index, output) in nonce.iter_mut().enumerate() {
1279 *output = u8::from_str_radix(&value[index * 2..index * 2 + 2], 16)
1280 .map_err(|_| WindowsError::InvalidBootstrap)?;
1281 }
1282 if nonce == [0; 32] {
1283 return Err(WindowsError::InvalidBootstrap);
1284 }
1285 Ok(nonce)
1286}
1287
1288fn last_os(operation: &'static str) -> WindowsError {
1289 WindowsError::Os {
1291 operation,
1292 code: unsafe { GetLastError() },
1293 }
1294}
1295
1296pub const fn status() -> BackendStatus {
1298 BackendStatus::Available
1299}
1300
1301#[cfg(test)]
1302mod tests {
1303 use super::*;
1304 use native_ipc_core::layout::{
1305 AcknowledgementRouteSpec, Endpoint, LayoutLimits, RegionSpec, RoleId,
1306 };
1307 use std::time::Duration;
1308 use windows_sys::Win32::System::Memory::{PAGE_EXECUTE_READWRITE, VirtualProtect};
1309
1310 fn topology() -> (RegionSetLayout, RoleId, RoleId) {
1311 let producer = RoleId::new(1).unwrap();
1312 let peer = RoleId::new(2).unwrap();
1313 let specs = [
1314 RegionSpec {
1315 role: producer,
1316 writer: Endpoint::Initiator,
1317 slot_count: 1,
1318 payload_bytes: 32,
1319 acknowledgement_count: 1,
1320 },
1321 RegionSpec {
1322 role: peer,
1323 writer: Endpoint::Responder,
1324 slot_count: 1,
1325 payload_bytes: 32,
1326 acknowledgement_count: 1,
1327 },
1328 ];
1329 let routes = [
1330 AcknowledgementRouteSpec {
1331 owner: peer,
1332 target: producer,
1333 slot_index: 0,
1334 cell_index: 0,
1335 },
1336 AcknowledgementRouteSpec {
1337 owner: producer,
1338 target: peer,
1339 slot_index: 0,
1340 cell_index: 0,
1341 },
1342 ];
1343 let topology = RegionSetLayout::calculate(
1344 [7; 32],
1345 23,
1346 &specs,
1347 &routes,
1348 LayoutLimits {
1349 maximum_mapping_size: 1 << 20,
1350 maximum_slot_count: 2,
1351 maximum_acknowledgement_count: 2,
1352 maximum_payload_bytes: 64,
1353 },
1354 )
1355 .unwrap();
1356 (topology, producer, peer)
1357 }
1358
1359 fn expected(topology: &RegionSetLayout, role: RoleId, len: usize) -> ValidationExpectations {
1360 let region = topology.region(role).unwrap();
1361 ValidationExpectations {
1362 schema_id: [7; 32],
1363 generation: 23,
1364 role,
1365 writer: region.writer(),
1366 maximum_mapping_size: len as u64,
1367 }
1368 }
1369
1370 #[test]
1371 fn nonce_is_nonzero_and_job_is_constructible() {
1372 assert_ne!(session_nonce().unwrap(), [0; 32]);
1373 let _job = ChildJob::new().unwrap();
1374 }
1375
1376 #[test]
1377 fn unnamed_section_is_page_rounded_and_zeroed() {
1378 let region = QuiescentRegion::new(37).unwrap();
1379 assert!(region.len() >= 37);
1380 assert!(region.as_bytes().iter().all(|byte| *byte == 0));
1381 let mut prior = 0;
1382 assert_eq!(
1384 unsafe {
1385 VirtualProtect(
1386 region.view.base.as_ptr().cast(),
1387 region.len(),
1388 PAGE_EXECUTE_READWRITE,
1389 &mut prior,
1390 )
1391 },
1392 0
1393 );
1394 }
1395
1396 #[test]
1397 fn read_only_duplicate_rejects_writable_mapping() {
1398 let region = QuiescentRegion::new(4096).unwrap();
1399 let duplicate = duplicate_to(
1400 region.section.0,
1401 unsafe { GetCurrentProcess() },
1402 FILE_MAP_READ,
1403 )
1404 .unwrap();
1405 let duplicate = OwnedHandle::new(duplicate.0 as HANDLE).unwrap();
1406 let denied = unsafe { MapViewOfFile(duplicate.0, FILE_MAP_WRITE, 0, 0, region.len()) };
1408 assert!(denied.Value.is_null());
1409 }
1410
1411 #[test]
1412 fn spawned_helper_is_pid_authenticated_and_job_owned() {
1413 let (topology, producer, peer) = topology();
1414 let producer_layout = topology.region(producer).unwrap();
1415 let mut producer_region =
1416 QuiescentRegion::new(producer_layout.total_size() as usize).unwrap();
1417 producer_layout
1418 .encode_into(producer_region.as_bytes_mut())
1419 .unwrap();
1420 let producer_expected = expected(&topology, producer, producer_region.len());
1421 let prepared_producer = producer_region
1422 .prepare_local_writer(producer_expected, topology.clone())
1423 .unwrap();
1424 let peer_layout = topology.region(peer).unwrap();
1425 let mut peer_region = QuiescentRegion::new(peer_layout.total_size() as usize).unwrap();
1426 peer_layout.encode_into(peer_region.as_bytes_mut()).unwrap();
1427 let peer_expected = expected(&topology, peer, peer_region.len());
1428 let prepared_peer = peer_region
1429 .prepare_remote_writer(peer_expected, topology.clone())
1430 .unwrap();
1431 let executable = std::env::current_exe().unwrap();
1432 let arguments = [
1433 OsString::from("--exact"),
1434 OsString::from("windows::tests::spawned_helper_entry"),
1435 OsString::from("--ignored"),
1436 OsString::from("--nocapture"),
1437 ];
1438 let mut child = ChildSession::spawn(&executable, &arguments).unwrap();
1439 assert_ne!(child.pid(), unsafe { GetCurrentProcessId() });
1440 let (mut writer, reader) = child
1441 .commit_transfers(prepared_producer, prepared_peer)
1442 .unwrap();
1443 writer
1444 .publish(0, 1, None, b"cross-process-windows")
1445 .unwrap();
1446 for _ in 0..10_000 {
1447 if let Ok(payload) = reader.copy_payload(0, 1) {
1448 assert_eq!(payload, b"child-windows-writer");
1449 child.wait().unwrap();
1450 return;
1451 }
1452 std::thread::sleep(Duration::from_millis(1));
1453 }
1454 panic!("child never published payload");
1455 }
1456
1457 #[test]
1458 fn helper_exit_before_connect_is_bounded() {
1459 let executable = std::env::current_exe().unwrap();
1460 let arguments = [
1461 OsString::from("--exact"),
1462 OsString::from("windows::tests::exit_before_connect_entry"),
1463 OsString::from("--ignored"),
1464 ];
1465 let result = ChildSession::spawn(&executable, &arguments);
1466 assert!(matches!(
1467 result,
1468 Err(WindowsError::ChildExit(0) | WindowsError::Os { .. })
1469 ));
1470 }
1471
1472 #[test]
1473 fn helper_stall_before_auth_is_bounded() {
1474 let executable = std::env::current_exe().unwrap();
1475 let arguments = [
1476 OsString::from("--exact"),
1477 OsString::from("windows::tests::stall_before_auth_entry"),
1478 OsString::from("--ignored"),
1479 ];
1480 let result = ChildSession::spawn(&executable, &arguments);
1481 assert!(matches!(result, Err(WindowsError::TimedOut("pipe read"))));
1482 }
1483
1484 #[test]
1485 #[ignore = "spawned only by the exact lifecycle test"]
1486 fn spawned_helper_entry() {
1487 let (topology, producer, peer) = topology();
1488 let mut channel = connect_spawned_helper().unwrap();
1489 assert_ne!(channel.parent_pid(), unsafe { GetCurrentProcessId() });
1490 let (reader_handle, reader_len, writer_handle, writer_len) =
1491 channel.receive_capabilities().unwrap();
1492 let reader = unsafe {
1494 channel
1495 .import_reader(
1496 reader_handle.0,
1497 reader_len,
1498 expected(&topology, producer, reader_len),
1499 topology.clone(),
1500 )
1501 .unwrap()
1502 };
1503 let writer = unsafe {
1505 channel
1506 .import_writer(
1507 writer_handle.0,
1508 writer_len,
1509 expected(&topology, peer, writer_len),
1510 topology,
1511 )
1512 .unwrap()
1513 };
1514 let (reader, mut writer) = channel.commit_imports(reader, writer).unwrap();
1515 for _ in 0..10_000 {
1516 if let Ok(payload) = reader.copy_payload(0, 1) {
1517 assert_eq!(payload, b"cross-process-windows");
1518 writer.publish(0, 1, None, b"child-windows-writer").unwrap();
1519 return;
1520 }
1521 std::thread::sleep(Duration::from_millis(1));
1522 }
1523 panic!("parent never published payload");
1524 }
1525
1526 fn detached_channel() -> ChildChannel {
1527 let nonce = session_nonce().unwrap();
1528 let name = format!(r"\\.\pipe\native-ipc-test-{}", hex(&nonce));
1529 let pipe_name = wide_null(OsStr::new(&name));
1530 let pipe = unsafe {
1532 CreateNamedPipeW(
1533 pipe_name.as_ptr(),
1534 PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE,
1535 PIPE_TYPE_MESSAGE
1536 | PIPE_READMODE_MESSAGE
1537 | PIPE_NOWAIT
1538 | PIPE_REJECT_REMOTE_CLIENTS,
1539 1,
1540 4096,
1541 4096,
1542 WAIT_MS,
1543 std::ptr::null(),
1544 )
1545 };
1546 ChildChannel {
1547 pipe: OwnedHandle::new(pipe).unwrap(),
1548 parent_pid: unsafe { GetCurrentProcessId() },
1549 nonce,
1550 channel_id: mint_channel_id(),
1551 next_transfer_id: 1,
1552 pending_transcript: None,
1553 poisoned: false,
1554 }
1555 }
1556
1557 #[test]
1558 fn foreign_pending_imports_fail_closed_before_ready() {
1559 let (topology, producer, peer) = topology();
1560 let producer_layout = topology.region(producer).unwrap();
1561 let mut producer_region =
1562 QuiescentRegion::new(producer_layout.total_size() as usize).unwrap();
1563 producer_layout
1564 .encode_into(producer_region.as_bytes_mut())
1565 .unwrap();
1566 let reader_len = producer_region.len();
1567 let reader_expected = expected(&topology, producer, reader_len);
1568 let reader_dup = duplicate_to(
1569 producer_region.section.0,
1570 unsafe { GetCurrentProcess() },
1571 FILE_MAP_READ,
1572 )
1573 .unwrap();
1574
1575 let peer_layout = topology.region(peer).unwrap();
1576 let mut peer_region = QuiescentRegion::new(peer_layout.total_size() as usize).unwrap();
1577 peer_layout.encode_into(peer_region.as_bytes_mut()).unwrap();
1578 let writer_len = peer_region.len();
1579 let writer_expected = expected(&topology, peer, writer_len);
1580 let writer_dup = duplicate_to(
1581 peer_region.section.0,
1582 unsafe { GetCurrentProcess() },
1583 FILE_MAP_WRITE,
1584 )
1585 .unwrap();
1586
1587 let first = detached_channel();
1588 let mut second = detached_channel();
1589 let reader = unsafe {
1591 first
1592 .import_reader(reader_dup.0, reader_len, reader_expected, topology.clone())
1593 .unwrap()
1594 };
1595 let writer = unsafe {
1597 first
1598 .import_writer(writer_dup.0, writer_len, writer_expected, topology)
1599 .unwrap()
1600 };
1601
1602 assert!(matches!(
1605 second.commit_imports(reader, writer),
1606 Err(WindowsError::ForeignPending)
1607 ));
1608 assert!(matches!(
1610 second.receive_capabilities(),
1611 Err(WindowsError::InvalidBootstrap)
1612 ));
1613 }
1614
1615 #[test]
1616 #[ignore = "spawned only by the exit-before-connect lifecycle test"]
1617 fn exit_before_connect_entry() {}
1618
1619 #[test]
1620 #[ignore = "spawned only by the stalled-auth lifecycle test"]
1621 fn stall_before_auth_entry() {
1622 let name = wide_null(&std::env::var_os(PIPE_ENV).unwrap());
1623 let pipe = unsafe {
1625 CreateFileW(
1626 name.as_ptr(),
1627 GENERIC_READ | GENERIC_WRITE,
1628 0,
1629 std::ptr::null(),
1630 OPEN_EXISTING,
1631 0,
1632 std::ptr::null_mut(),
1633 )
1634 };
1635 let _pipe = OwnedHandle::new(pipe).unwrap();
1636 std::thread::sleep(Duration::from_secs(5));
1637 }
1638}