Skip to main content

native_ipc_platform/
windows.rs

1//! Windows unnamed-section mappings, pipe identity, and Job containment.
2
3use 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;
49
50/// Windows section, bootstrap, lifecycle, or binding failure.
51#[derive(Debug)]
52pub enum WindowsError {
53    /// Win32 API failed with a captured `GetLastError` value.
54    Os {
55        /// Bounded Win32 operation name.
56        operation: &'static str,
57        /// Captured `GetLastError` value.
58        code: u32,
59    },
60    /// Mapping size is zero or cannot be page-rounded.
61    InvalidSize(usize),
62    /// Named-pipe peer PID differs from the held expected process.
63    WrongPeer,
64    /// Received or duplicated handle is invalid for this process.
65    InvalidHandle,
66    /// Quiescent layout validation failed.
67    Layout(native_ipc_core::layout::LayoutError),
68    /// Audited core binding failed.
69    Binding(BindingError),
70    /// Bootstrap environment or authenticated handshake was malformed.
71    InvalidBootstrap,
72    /// A sole-writer capability was already duplicated from this preparation.
73    CapabilityAlreadyTransferred,
74    /// A bounded bootstrap or lifecycle operation reached its deadline.
75    TimedOut(&'static str),
76    /// The exact helper exited unsuccessfully.
77    ChildExit(u32),
78}
79
80impl fmt::Display for WindowsError {
81    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
82        write!(formatter, "Windows transport failed: {self:?}")
83    }
84}
85impl std::error::Error for WindowsError {}
86impl From<native_ipc_core::layout::LayoutError> for WindowsError {
87    fn from(value: native_ipc_core::layout::LayoutError) -> Self {
88        Self::Layout(value)
89    }
90}
91impl From<BindingError> for WindowsError {
92    fn from(value: BindingError) -> Self {
93        Self::Binding(value)
94    }
95}
96
97/// Generates a nonzero 256-bit bootstrap nonce from the system RNG.
98pub fn session_nonce() -> Result<[u8; 32], WindowsError> {
99    let mut nonce = [0_u8; 32];
100    // SAFETY: output buffer is valid; null algorithm selects system-preferred RNG.
101    let status = unsafe {
102        BCryptGenRandom(
103            std::ptr::null_mut(),
104            nonce.as_mut_ptr(),
105            nonce.len() as u32,
106            BCRYPT_USE_SYSTEM_PREFERRED_RNG,
107        )
108    };
109    if status < 0 || nonce == [0; 32] {
110        Err(last_os("BCryptGenRandom"))
111    } else {
112        Ok(nonce)
113    }
114}
115
116/// Verifies the connected client of a private named-pipe server instance.
117///
118/// # Safety
119///
120/// `pipe` must be a live connected named-pipe server handle owned by the caller.
121pub unsafe fn authenticate_pipe_client(
122    pipe: HANDLE,
123    expected_pid: u32,
124) -> Result<(), WindowsError> {
125    let mut actual = 0;
126    // SAFETY: caller supplies a live pipe and output pointer is valid.
127    if unsafe { GetNamedPipeClientProcessId(pipe, &mut actual) } == 0 {
128        return Err(last_os("GetNamedPipeClientProcessId"));
129    }
130    if actual == expected_pid {
131        Ok(())
132    } else {
133        Err(WindowsError::WrongPeer)
134    }
135}
136
137/// Verifies the connected server of a private named-pipe client instance.
138///
139/// # Safety
140///
141/// `pipe` must be a live connected named-pipe client handle owned by the caller.
142pub unsafe fn authenticate_pipe_server(
143    pipe: HANDLE,
144    expected_pid: u32,
145) -> Result<(), WindowsError> {
146    let mut actual = 0;
147    // SAFETY: caller supplies a live pipe and output pointer is valid.
148    if unsafe { GetNamedPipeServerProcessId(pipe, &mut actual) } == 0 {
149        return Err(last_os("GetNamedPipeServerProcessId"));
150    }
151    if actual == expected_pid {
152        Ok(())
153    } else {
154        Err(WindowsError::WrongPeer)
155    }
156}
157
158/// Quiescent unnamed paging-file section and exclusive initialization view.
159pub struct QuiescentRegion {
160    section: OwnedHandle,
161    view: View,
162    logical_len: usize,
163}
164
165impl QuiescentRegion {
166    /// Allocates a page-rounded unnamed, non-executable section.
167    pub fn new(logical_len: usize) -> Result<Self, WindowsError> {
168        let len = page_align(logical_len)?;
169        let size = len as u64;
170        // SAFETY: paging-file sentinel, null security/name, and checked size are valid.
171        let section = unsafe {
172            CreateFileMappingW(
173                INVALID_HANDLE_VALUE,
174                std::ptr::null(),
175                PAGE_READWRITE | SEC_COMMIT,
176                (size >> 32) as u32,
177                size as u32,
178                std::ptr::null(),
179            )
180        };
181        let section = OwnedHandle::new(section)?;
182        let view = View::map(section.0, len, FILE_MAP_WRITE)?;
183        // SAFETY: newly created unnamed section view is exclusive and writable.
184        unsafe { std::slice::from_raw_parts_mut(view.base.as_ptr(), len) }.fill(0);
185        Ok(Self {
186            section,
187            view,
188            logical_len,
189        })
190    }
191    /// Exact page-rounded capability size.
192    pub const fn len(&self) -> usize {
193        self.view.len
194    }
195    /// Returns whether the capability is empty (always false for valid values).
196    pub const fn is_empty(&self) -> bool {
197        false
198    }
199    /// Requested logical layout length.
200    pub const fn logical_len(&self) -> usize {
201        self.logical_len
202    }
203    /// Full quiescent initialization range.
204    pub fn as_bytes(&self) -> &[u8] {
205        // SAFETY: no duplicated handle or second view exists in this typestate.
206        unsafe { std::slice::from_raw_parts(self.view.base.as_ptr(), self.view.len) }
207    }
208    /// Mutable full quiescent initialization range.
209    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
210        // SAFETY: `&mut self` and typestate provide exclusivity.
211        unsafe { std::slice::from_raw_parts_mut(self.view.base.as_ptr(), self.view.len) }
212    }
213
214    /// Validates a future local-writer region before attenuated duplication.
215    pub fn prepare_local_writer(
216        self,
217        expected: ValidationExpectations,
218        topology: RegionSetLayout,
219    ) -> Result<PreparedLocalWriter, WindowsError> {
220        // SAFETY: section is quiescent and complete capability range is borrowed.
221        let layout =
222            unsafe { ValidatedRegionLayout::validate(self.as_bytes(), expected, &topology) }?;
223        Ok(PreparedLocalWriter {
224            section: self.section,
225            view: self.view,
226            layout,
227            topology,
228        })
229    }
230
231    /// Validates a future remote-writer region before remapping local read-only.
232    pub fn prepare_remote_writer(
233        self,
234        expected: ValidationExpectations,
235        topology: RegionSetLayout,
236    ) -> Result<PreparedRemoteWriter, WindowsError> {
237        // SAFETY: section is quiescent and complete capability range is borrowed.
238        let layout =
239            unsafe { ValidatedRegionLayout::validate(self.as_bytes(), expected, &topology) }?;
240        let len = self.view.len;
241        drop(self.view);
242        let view = View::map(self.section.0, len, FILE_MAP_READ)?;
243        Ok(PreparedRemoteWriter {
244            section: self.section,
245            view,
246            layout,
247            topology,
248            writer_duplicated: false,
249        })
250    }
251}
252
253/// Target-process handle value produced by exact-rights duplication.
254#[derive(Clone, Copy, Debug, Eq, PartialEq)]
255pub struct RemoteHandle(pub usize);
256
257/// Local unique writer awaiting a read-only peer handle and READY barrier.
258pub struct PreparedLocalWriter {
259    section: OwnedHandle,
260    view: View,
261    layout: ValidatedRegionLayout,
262    topology: RegionSetLayout,
263}
264impl PreparedLocalWriter {
265    /// Duplicates exactly `FILE_MAP_READ` into a held authenticated target process.
266    ///
267    /// # Safety
268    ///
269    /// `target_process` must be the held live process authenticated by the pipe.
270    pub unsafe fn duplicate_reader_to(
271        &self,
272        target_process: HANDLE,
273    ) -> Result<RemoteHandle, WindowsError> {
274        duplicate_to(self.section.0, target_process, FILE_MAP_READ)
275    }
276    /// Closes the full section handle and commits the sole local writer.
277    pub fn bind(self) -> Result<WriterRegion<WindowsWriterMapping>, WindowsError> {
278        drop(self.section);
279        Ok(WriterRegion::new(
280            WindowsWriterMapping { view: self.view },
281            self.layout,
282            self.topology,
283        )?)
284    }
285}
286
287/// Local read-only view awaiting the sole remote-writer handle and READY barrier.
288pub struct PreparedRemoteWriter {
289    section: OwnedHandle,
290    view: View,
291    layout: ValidatedRegionLayout,
292    topology: RegionSetLayout,
293    writer_duplicated: bool,
294}
295impl PreparedRemoteWriter {
296    /// Duplicates exactly one `FILE_MAP_WRITE` handle into a held authenticated target.
297    ///
298    /// # Safety
299    ///
300    /// `target_process` must be the held live process authenticated by the pipe.
301    pub unsafe fn duplicate_writer_to(
302        &mut self,
303        target_process: HANDLE,
304    ) -> Result<RemoteHandle, WindowsError> {
305        if self.writer_duplicated {
306            return Err(WindowsError::CapabilityAlreadyTransferred);
307        }
308        let handle = duplicate_to(self.section.0, target_process, FILE_MAP_WRITE)?;
309        self.writer_duplicated = true;
310        Ok(handle)
311    }
312    /// Closes the full section handle and commits the local read-only witness.
313    pub fn bind_reader(self) -> Result<ReaderRegion<WindowsReaderMapping>, WindowsError> {
314        drop(self.section);
315        Ok(ReaderRegion::new(
316            WindowsReaderMapping { view: self.view },
317            self.layout,
318            self.topology,
319        )?)
320    }
321}
322
323/// Imports a duplicated section handle using an exact desired mapping access.
324///
325/// # Safety
326///
327/// `handle` must have arrived over the authenticated bootstrap, be owned by this
328/// process, have exactly the manifest rights, and not have been previously closed.
329pub unsafe fn import_reader(
330    handle: usize,
331    len: usize,
332    expected: ValidationExpectations,
333    topology: RegionSetLayout,
334) -> Result<ReaderRegion<WindowsReaderMapping>, WindowsError> {
335    let section = OwnedHandle::new(handle as HANDLE)?;
336    let view = View::map(section.0, len, FILE_MAP_READ)?;
337    // SAFETY: READY protocol keeps view quiescent; access is read-only.
338    let bytes = unsafe { std::slice::from_raw_parts(view.base.as_ptr(), len) };
339    let layout = unsafe { ValidatedRegionLayout::validate(bytes, expected, &topology) }?;
340    drop(section);
341    Ok(ReaderRegion::new(
342        WindowsReaderMapping { view },
343        layout,
344        topology,
345    )?)
346}
347
348/// Imports the sole duplicated writer handle.
349///
350/// # Safety
351///
352/// Same authenticated ownership requirements as [`import_reader`], plus the
353/// manifest/creator must guarantee no other writable handle or view exists.
354pub unsafe fn import_writer(
355    handle: usize,
356    len: usize,
357    expected: ValidationExpectations,
358    topology: RegionSetLayout,
359) -> Result<WriterRegion<WindowsWriterMapping>, WindowsError> {
360    let section = OwnedHandle::new(handle as HANDLE)?;
361    let view = View::map(section.0, len, FILE_MAP_WRITE)?;
362    // SAFETY: READY protocol keeps the sole writer quiescent during validation.
363    let bytes = unsafe { std::slice::from_raw_parts(view.base.as_ptr(), len) };
364    let layout = unsafe { ValidatedRegionLayout::validate(bytes, expected, &topology) }?;
365    drop(section);
366    Ok(WriterRegion::new(
367        WindowsWriterMapping { view },
368        layout,
369        topology,
370    )?)
371}
372
373/// Kill-on-last-handle Job Object used to contain an exact spawned helper tree.
374pub struct ChildJob(OwnedHandle);
375impl ChildJob {
376    /// Creates an unnamed non-inheritable kill-on-close job.
377    pub fn new() -> Result<Self, WindowsError> {
378        // SAFETY: null security/name create an unnamed non-inheritable job.
379        let handle = unsafe { CreateJobObjectW(std::ptr::null(), std::ptr::null()) };
380        let handle = OwnedHandle::new(handle)?;
381        let mut information: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = unsafe { zeroed() };
382        information.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
383        // SAFETY: buffer type/size match the requested information class.
384        if unsafe {
385            SetInformationJobObject(
386                handle.0,
387                JobObjectExtendedLimitInformation,
388                (&information as *const JOBOBJECT_EXTENDED_LIMIT_INFORMATION).cast(),
389                size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() as u32,
390            )
391        } == 0
392        {
393            return Err(last_os("SetInformationJobObject"));
394        }
395        Ok(Self(handle))
396    }
397    /// Assigns a still-suspended exact child before any untrusted code runs.
398    ///
399    /// # Safety
400    ///
401    /// `process` must be the live suspended child handle returned by CreateProcess.
402    pub unsafe fn assign_suspended(&self, process: HANDLE) -> Result<(), WindowsError> {
403        // SAFETY: caller proves process handle/lifecycle; job handle is live.
404        if unsafe { AssignProcessToJobObject(self.0.0, process) } == 0 {
405            Err(last_os("AssignProcessToJobObject"))
406        } else {
407            Ok(())
408        }
409    }
410}
411
412const PIPE_ENV: &str = "NATIVE_IPC_WINDOWS_PIPE";
413const NONCE_ENV: &str = "NATIVE_IPC_WINDOWS_NONCE";
414const PARENT_ENV: &str = "NATIVE_IPC_PARENT_PID";
415const BOOTSTRAP_MAGIC: [u8; 8] = *b"NIPCWIN1";
416const AUTH_MAGIC: [u8; 8] = *b"NIPCAUT1";
417const READY_MAGIC: [u8; 8] = *b"NIPCRDY1";
418const CAPABILITY_MAGIC: [u8; 8] = *b"NIPCCAP1";
419#[cfg(not(test))]
420const WAIT_MS: u32 = 10_000;
421#[cfg(test)]
422const WAIT_MS: u32 = 1_000;
423
424#[repr(C)]
425#[derive(Clone, Copy)]
426struct BootstrapFrame {
427    magic: [u8; 8],
428    nonce: [u8; 32],
429    parent_pid: u32,
430    child_pid: u32,
431}
432
433#[repr(C)]
434#[derive(Clone, Copy)]
435struct CapabilityFrame {
436    magic: [u8; 8],
437    reader_handle: usize,
438    writer_handle: usize,
439    reader_len: usize,
440    writer_len: usize,
441}
442
443/// Parent-owned exact helper, private pipe, process handle, and kill-on-close job.
444pub struct ChildSession {
445    pipe: OwnedHandle,
446    process: OwnedHandle,
447    _job: ChildJob,
448    pid: u32,
449    nonce: [u8; 32],
450    reaped: bool,
451}
452
453impl ChildSession {
454    /// Creates a one-instance local pipe and launches the helper suspended.
455    pub fn spawn(path: &Path, arguments: &[OsString]) -> Result<Self, WindowsError> {
456        let nonce = session_nonce()?;
457        let name = format!(r"\\.\pipe\native-ipc-{}", hex(&nonce));
458        let pipe_name = wide_null(OsStr::new(&name));
459        // SAFETY: name is terminated; null security creates a non-inheritable handle.
460        let pipe = unsafe {
461            CreateNamedPipeW(
462                pipe_name.as_ptr(),
463                PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE,
464                PIPE_TYPE_MESSAGE
465                    | PIPE_READMODE_MESSAGE
466                    | PIPE_NOWAIT
467                    | PIPE_REJECT_REMOTE_CLIENTS,
468                1,
469                4096,
470                4096,
471                WAIT_MS,
472                std::ptr::null(),
473            )
474        };
475        let pipe = OwnedHandle::new(pipe)?;
476        let job = ChildJob::new()?;
477
478        let application = wide_null(path.as_os_str());
479        let mut command = command_line(path.as_os_str(), arguments);
480        let parent_pid = unsafe { GetCurrentProcessId() };
481        let environment = environment_block(&[
482            (PIPE_ENV, name),
483            (NONCE_ENV, hex(&nonce)),
484            (PARENT_ENV, parent_pid.to_string()),
485        ]);
486        let mut startup: STARTUPINFOW = unsafe { zeroed() };
487        startup.cb = size_of::<STARTUPINFOW>() as u32;
488        let mut information: PROCESS_INFORMATION = unsafe { zeroed() };
489        // SAFETY: all UTF-16 buffers and output structures remain live; no handles inherit.
490        if unsafe {
491            CreateProcessW(
492                application.as_ptr(),
493                command.as_mut_ptr(),
494                std::ptr::null(),
495                std::ptr::null(),
496                0,
497                CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT,
498                environment.as_ptr().cast(),
499                std::ptr::null(),
500                &startup,
501                &mut information,
502            )
503        } == 0
504        {
505            return Err(last_os("CreateProcessW"));
506        }
507        let process = OwnedHandle::new(information.hProcess)?;
508        let thread = OwnedHandle::new(information.hThread)?;
509        // SAFETY: CreateProcessW returned this exact child still suspended.
510        if let Err(error) = unsafe { job.assign_suspended(process.0) } {
511            // SAFETY: exact held child is still suspended.
512            let _ = unsafe { TerminateProcess(process.0, 127) };
513            return Err(error);
514        }
515        // SAFETY: thread is the exact suspended primary thread.
516        if unsafe { ResumeThread(thread.0) } == u32::MAX {
517            let error = last_os("ResumeThread");
518            let _ = unsafe { TerminateProcess(process.0, 127) };
519            return Err(error);
520        }
521        drop(thread);
522        connect_pipe(pipe.0, process.0)?;
523        // SAFETY: the server pipe is connected and the expected PID is held live.
524        unsafe { authenticate_pipe_client(pipe.0, information.dwProcessId)? };
525        let hello = BootstrapFrame {
526            magic: BOOTSTRAP_MAGIC,
527            nonce,
528            parent_pid,
529            child_pid: information.dwProcessId,
530        };
531        write_frame(pipe.0, &hello)?;
532        let ready = read_frame(pipe.0)?;
533        if ready.magic != AUTH_MAGIC
534            || ready.nonce != nonce
535            || ready.parent_pid != parent_pid
536            || ready.child_pid != information.dwProcessId
537        {
538            let _ = unsafe { TerminateProcess(process.0, 127) };
539            return Err(WindowsError::InvalidBootstrap);
540        }
541        Ok(Self {
542            pipe,
543            process,
544            _job: job,
545            pid: information.dwProcessId,
546            nonce,
547            reaped: false,
548        })
549    }
550
551    /// Exact live process handle used only for attenuated handle duplication.
552    pub const fn process_handle(&self) -> HANDLE {
553        self.process.0
554    }
555    /// Kernel-created child process ID authenticated on the private pipe.
556    pub const fn pid(&self) -> u32 {
557        self.pid
558    }
559    /// Sends the two exact-rights handle values and their complete mapped lengths.
560    pub fn send_capabilities(
561        &self,
562        reader: RemoteHandle,
563        reader_len: usize,
564        writer: RemoteHandle,
565        writer_len: usize,
566    ) -> Result<(), WindowsError> {
567        write_pod(
568            self.pipe.0,
569            &CapabilityFrame {
570                magic: CAPABILITY_MAGIC,
571                reader_handle: reader.0,
572                writer_handle: writer.0,
573                reader_len,
574                writer_len,
575            },
576        )
577    }
578    /// Waits for the authenticated child to finish import and validation.
579    pub fn wait_ready(&self) -> Result<(), WindowsError> {
580        let ready: BootstrapFrame = read_pod(self.pipe.0)?;
581        if ready.magic == READY_MAGIC
582            && ready.nonce == self.nonce
583            && ready.parent_pid == unsafe { GetCurrentProcessId() }
584            && ready.child_pid == self.pid
585        {
586            Ok(())
587        } else {
588            Err(WindowsError::InvalidBootstrap)
589        }
590    }
591    /// Waits for a normal helper exit after protocol completion.
592    pub fn wait(mut self) -> Result<(), WindowsError> {
593        // SAFETY: process is held live for this session.
594        match unsafe { WaitForSingleObject(self.process.0, WAIT_MS) } {
595            WAIT_OBJECT_0 => {
596                let mut code = 0;
597                // SAFETY: held process is signaled and output pointer is valid.
598                if unsafe { GetExitCodeProcess(self.process.0, &mut code) } == 0 {
599                    return Err(last_os("GetExitCodeProcess"));
600                }
601                if code != 0 {
602                    return Err(WindowsError::ChildExit(code));
603                }
604            }
605            WAIT_TIMEOUT => return Err(WindowsError::TimedOut("helper exit")),
606            _ => return Err(last_os("WaitForSingleObject")),
607        }
608        self.reaped = true;
609        Ok(())
610    }
611}
612
613impl Drop for ChildSession {
614    fn drop(&mut self) {
615        if !self.reaped {
616            // SAFETY: exact held child; job close remains the backstop for descendants.
617            let _ = unsafe { TerminateProcess(self.process.0, 127) };
618            let _ = unsafe { WaitForSingleObject(self.process.0, WAIT_MS) };
619        }
620    }
621}
622
623/// Connects a spawned helper from its authenticated bootstrap environment.
624pub fn connect_spawned_helper() -> Result<ChildChannel, WindowsError> {
625    let name = std::env::var_os(PIPE_ENV).ok_or(WindowsError::InvalidBootstrap)?;
626    let nonce =
627        parse_nonce(&std::env::var(NONCE_ENV).map_err(|_| WindowsError::InvalidBootstrap)?)?;
628    let parent_pid = std::env::var(PARENT_ENV)
629        .map_err(|_| WindowsError::InvalidBootstrap)?
630        .parse::<u32>()
631        .map_err(|_| WindowsError::InvalidBootstrap)?;
632    let name = wide_null(&name);
633    // SAFETY: terminated name; no sharing or inheritance and existing pipe only.
634    let pipe = unsafe {
635        CreateFileW(
636            name.as_ptr(),
637            GENERIC_READ | GENERIC_WRITE,
638            0,
639            std::ptr::null(),
640            OPEN_EXISTING,
641            0,
642            std::ptr::null_mut(),
643        )
644    };
645    let pipe = OwnedHandle::new(pipe)?;
646    let mode = PIPE_READMODE_MESSAGE | PIPE_NOWAIT;
647    // SAFETY: connected client pipe and mode pointer are valid.
648    if unsafe { SetNamedPipeHandleState(pipe.0, &mode, std::ptr::null(), std::ptr::null()) } == 0 {
649        return Err(last_os("SetNamedPipeHandleState"));
650    }
651    // SAFETY: connected pipe client and exact expected parent from spawn environment.
652    unsafe { authenticate_pipe_server(pipe.0, parent_pid)? };
653    let hello = read_frame(pipe.0)?;
654    let child_pid = unsafe { GetCurrentProcessId() };
655    if hello.magic != BOOTSTRAP_MAGIC
656        || hello.nonce != nonce
657        || hello.parent_pid != parent_pid
658        || hello.child_pid != child_pid
659    {
660        return Err(WindowsError::InvalidBootstrap);
661    }
662    write_frame(
663        pipe.0,
664        &BootstrapFrame {
665            magic: AUTH_MAGIC,
666            ..hello
667        },
668    )?;
669    Ok(ChildChannel {
670        pipe,
671        parent_pid,
672        nonce,
673    })
674}
675
676/// Authenticated child endpoint retained for the lifetime of imported capabilities.
677pub struct ChildChannel {
678    pipe: OwnedHandle,
679    parent_pid: u32,
680    nonce: [u8; 32],
681}
682impl ChildChannel {
683    /// Held authenticated parent PID.
684    pub const fn parent_pid(&self) -> u32 {
685        self.parent_pid
686    }
687    /// Raw pipe handle for a bounded manifest protocol owned by the caller.
688    pub const fn pipe_handle(&self) -> HANDLE {
689        self.pipe.0
690    }
691    /// Receives exact-rights handle values only after pipe PID authentication.
692    pub fn receive_capabilities(
693        &self,
694    ) -> Result<(RemoteHandle, usize, RemoteHandle, usize), WindowsError> {
695        let frame: CapabilityFrame = read_pod(self.pipe.0)?;
696        if frame.magic != CAPABILITY_MAGIC
697            || frame.reader_handle == 0
698            || frame.writer_handle == 0
699            || frame.reader_len == 0
700            || frame.writer_len == 0
701        {
702            return Err(WindowsError::InvalidBootstrap);
703        }
704        Ok((
705            RemoteHandle(frame.reader_handle),
706            frame.reader_len,
707            RemoteHandle(frame.writer_handle),
708            frame.writer_len,
709        ))
710    }
711    /// Signals that received mappings were imported and validated.
712    pub fn signal_ready(&self) -> Result<(), WindowsError> {
713        write_pod(
714            self.pipe.0,
715            &BootstrapFrame {
716                magic: READY_MAGIC,
717                nonce: self.nonce,
718                parent_pid: self.parent_pid,
719                child_pid: unsafe { GetCurrentProcessId() },
720            },
721        )
722    }
723}
724
725/// Platform-minted unique writable unnamed-section view.
726pub struct WindowsWriterMapping {
727    view: View,
728}
729// SAFETY: constructors consume the full creator handle and retain the sole RW view.
730unsafe impl SoleWriterMapping for WindowsWriterMapping {
731    fn base(&self) -> NonNull<u8> {
732        self.view.base
733    }
734    fn len(&self) -> usize {
735        self.view.len
736    }
737}
738/// Platform-minted read-only unnamed-section view.
739pub struct WindowsReaderMapping {
740    view: View,
741}
742// SAFETY: constructors map only FILE_MAP_READ and retain the view lifetime.
743unsafe impl ReadOnlyMapping for WindowsReaderMapping {
744    fn base(&self) -> NonNull<u8> {
745        self.view.base
746    }
747    fn len(&self) -> usize {
748        self.view.len
749    }
750}
751
752struct OwnedHandle(HANDLE);
753impl OwnedHandle {
754    fn new(handle: HANDLE) -> Result<Self, WindowsError> {
755        if handle.is_null() || handle == INVALID_HANDLE_VALUE {
756            Err(WindowsError::InvalidHandle)
757        } else {
758            Ok(Self(handle))
759        }
760    }
761}
762impl Drop for OwnedHandle {
763    fn drop(&mut self) {
764        // SAFETY: this value uniquely owns a real non-pseudo handle.
765        let _ = unsafe { CloseHandle(self.0) };
766    }
767}
768
769struct View {
770    base: NonNull<u8>,
771    len: usize,
772}
773impl View {
774    fn map(section: HANDLE, len: usize, access: u32) -> Result<Self, WindowsError> {
775        // SAFETY: section handle is live; access/offset/length are checked.
776        let address = unsafe { MapViewOfFile(section, access, 0, 0, len) };
777        let base = NonNull::new(address.Value.cast()).ok_or_else(|| last_os("MapViewOfFile"))?;
778        Ok(Self { base, len })
779    }
780}
781impl Drop for View {
782    fn drop(&mut self) {
783        // SAFETY: this object uniquely owns the mapped view.
784        let _ = unsafe {
785            UnmapViewOfFile(MEMORY_MAPPED_VIEW_ADDRESS {
786                Value: self.base.as_ptr().cast(),
787            })
788        };
789    }
790}
791
792fn duplicate_to(source: HANDLE, target: HANDLE, access: u32) -> Result<RemoteHandle, WindowsError> {
793    let mut remote: HANDLE = std::ptr::null_mut();
794    // SAFETY: source/current/target handles are live; no SAME_ACCESS option is used.
795    if unsafe {
796        DuplicateHandle(
797            GetCurrentProcess(),
798            source,
799            target,
800            &mut remote,
801            access,
802            0,
803            0,
804        )
805    } == 0
806    {
807        return Err(last_os("DuplicateHandle"));
808    }
809    if remote.is_null() {
810        Err(WindowsError::InvalidHandle)
811    } else {
812        Ok(RemoteHandle(remote as usize))
813    }
814}
815
816fn page_align(size: usize) -> Result<usize, WindowsError> {
817    if size == 0 {
818        return Err(WindowsError::InvalidSize(size));
819    }
820    let mut information: SYSTEM_INFO = unsafe { zeroed() };
821    // SAFETY: output pointer is valid.
822    unsafe { GetSystemInfo(&mut information) };
823    let page = information.dwPageSize as usize;
824    if page == 0 || !page.is_power_of_two() {
825        return Err(WindowsError::InvalidSize(size));
826    }
827    size.checked_add(page - 1)
828        .map(|value| value & !(page - 1))
829        .filter(|value| *value <= isize::MAX as usize)
830        .ok_or(WindowsError::InvalidSize(size))
831}
832
833fn wide_null(value: &OsStr) -> Vec<u16> {
834    value.encode_wide().chain(std::iter::once(0)).collect()
835}
836
837fn quote_argument(value: &OsStr, output: &mut Vec<u16>) {
838    let units: Vec<u16> = value.encode_wide().collect();
839    let needs_quotes = units.is_empty()
840        || units
841            .iter()
842            .any(|unit| *unit == b' ' as u16 || *unit == b'\t' as u16);
843    if !needs_quotes {
844        output.extend(units);
845        return;
846    }
847    output.push(b'"' as u16);
848    let mut slashes = 0;
849    for unit in units {
850        if unit == b'\\' as u16 {
851            slashes += 1;
852        } else if unit == b'"' as u16 {
853            output.extend(std::iter::repeat_n(b'\\' as u16, slashes * 2 + 1));
854            output.push(unit);
855            slashes = 0;
856        } else {
857            output.extend(std::iter::repeat_n(b'\\' as u16, slashes));
858            output.push(unit);
859            slashes = 0;
860        }
861    }
862    output.extend(std::iter::repeat_n(b'\\' as u16, slashes * 2));
863    output.push(b'"' as u16);
864}
865
866fn command_line(path: &OsStr, arguments: &[OsString]) -> Vec<u16> {
867    let mut result = Vec::new();
868    quote_argument(path, &mut result);
869    for argument in arguments {
870        result.push(b' ' as u16);
871        quote_argument(argument, &mut result);
872    }
873    result.push(0);
874    result
875}
876
877fn environment_block(overrides: &[(&str, String)]) -> Vec<u16> {
878    let mut values: Vec<(OsString, OsString)> = std::env::vars_os()
879        .filter(|(key, _)| {
880            !overrides
881                .iter()
882                .any(|(name, _)| key.to_string_lossy().eq_ignore_ascii_case(name))
883        })
884        .collect();
885    values.extend(
886        overrides
887            .iter()
888            .map(|(key, value)| (OsString::from(key), OsString::from(value))),
889    );
890    values.sort_by(|left, right| {
891        left.0
892            .to_string_lossy()
893            .to_ascii_lowercase()
894            .cmp(&right.0.to_string_lossy().to_ascii_lowercase())
895    });
896    let mut block = Vec::new();
897    for (key, value) in values {
898        block.extend(key.encode_wide());
899        block.push(b'=' as u16);
900        block.extend(value.encode_wide());
901        block.push(0);
902    }
903    block.push(0);
904    block
905}
906
907fn pod_bytes<T>(value: &T) -> &[u8] {
908    // SAFETY: callers use repr(C), fully initialized integer-only protocol records.
909    unsafe { std::slice::from_raw_parts((value as *const T).cast(), size_of::<T>()) }
910}
911
912fn write_frame(pipe: HANDLE, frame: &BootstrapFrame) -> Result<(), WindowsError> {
913    write_pod(pipe, frame)
914}
915
916fn write_pod<T>(pipe: HANDLE, value: &T) -> Result<(), WindowsError> {
917    let bytes = pod_bytes(value);
918    let deadline = Instant::now() + Duration::from_millis(WAIT_MS.into());
919    loop {
920        let mut written = 0;
921        // SAFETY: pipe is live, bytes are valid, and nonblocking operation is synchronous.
922        if unsafe {
923            WriteFile(
924                pipe,
925                bytes.as_ptr(),
926                bytes.len() as u32,
927                &mut written,
928                std::ptr::null_mut(),
929            )
930        } != 0
931        {
932            return if written as usize == bytes.len() {
933                Ok(())
934            } else {
935                Err(WindowsError::InvalidBootstrap)
936            };
937        }
938        let code = unsafe { GetLastError() };
939        if code != ERROR_NO_DATA && code != ERROR_PIPE_LISTENING {
940            return Err(WindowsError::Os {
941                operation: "WriteFile",
942                code,
943            });
944        }
945        wait_retry(deadline, "pipe write")?;
946    }
947}
948
949fn read_frame(pipe: HANDLE) -> Result<BootstrapFrame, WindowsError> {
950    read_pod(pipe)
951}
952
953fn read_pod<T>(pipe: HANDLE) -> Result<T, WindowsError> {
954    let deadline = Instant::now() + Duration::from_millis(WAIT_MS.into());
955    loop {
956        let mut value: T = unsafe { zeroed() };
957        let mut read = 0;
958        // SAFETY: frame output range is valid and nonblocking operation is synchronous.
959        if unsafe {
960            ReadFile(
961                pipe,
962                (&mut value as *mut T).cast(),
963                size_of::<T>() as u32,
964                &mut read,
965                std::ptr::null_mut(),
966            )
967        } != 0
968        {
969            return if read as usize == size_of::<T>() {
970                Ok(value)
971            } else {
972                Err(WindowsError::InvalidBootstrap)
973            };
974        }
975        let code = unsafe { GetLastError() };
976        if code != ERROR_NO_DATA && code != ERROR_PIPE_LISTENING {
977            return Err(WindowsError::Os {
978                operation: "ReadFile",
979                code,
980            });
981        }
982        wait_retry(deadline, "pipe read")?;
983    }
984}
985
986fn connect_pipe(pipe: HANDLE, process: HANDLE) -> Result<(), WindowsError> {
987    let deadline = Instant::now() + Duration::from_millis(WAIT_MS.into());
988    loop {
989        // SAFETY: server pipe is nonblocking and no OVERLAPPED operation is requested.
990        if unsafe { ConnectNamedPipe(pipe, std::ptr::null_mut()) } != 0 {
991            return Ok(());
992        }
993        let code = unsafe { GetLastError() };
994        if code == ERROR_PIPE_CONNECTED {
995            return Ok(());
996        }
997        if code != ERROR_PIPE_LISTENING && code != ERROR_NO_DATA {
998            return Err(WindowsError::Os {
999                operation: "ConnectNamedPipe",
1000                code,
1001            });
1002        }
1003        // SAFETY: exact child process handle is held throughout bootstrap.
1004        if unsafe { WaitForSingleObject(process, 0) } == WAIT_OBJECT_0 {
1005            let mut exit = 0;
1006            // SAFETY: process is signaled and output is valid.
1007            if unsafe { GetExitCodeProcess(process, &mut exit) } != 0 {
1008                return Err(WindowsError::ChildExit(exit));
1009            }
1010            return Err(last_os("GetExitCodeProcess"));
1011        }
1012        wait_retry(deadline, "pipe connect")?;
1013    }
1014}
1015
1016fn wait_retry(deadline: Instant, operation: &'static str) -> Result<(), WindowsError> {
1017    if Instant::now() >= deadline {
1018        Err(WindowsError::TimedOut(operation))
1019    } else {
1020        std::thread::sleep(Duration::from_millis(1));
1021        Ok(())
1022    }
1023}
1024
1025fn hex(bytes: &[u8; 32]) -> String {
1026    let mut output = String::with_capacity(64);
1027    for byte in bytes {
1028        use std::fmt::Write as _;
1029        write!(&mut output, "{byte:02x}").expect("String writes are infallible");
1030    }
1031    output
1032}
1033
1034fn parse_nonce(value: &str) -> Result<[u8; 32], WindowsError> {
1035    if value.len() != 64 {
1036        return Err(WindowsError::InvalidBootstrap);
1037    }
1038    let mut nonce = [0; 32];
1039    for (index, output) in nonce.iter_mut().enumerate() {
1040        *output = u8::from_str_radix(&value[index * 2..index * 2 + 2], 16)
1041            .map_err(|_| WindowsError::InvalidBootstrap)?;
1042    }
1043    if nonce == [0; 32] {
1044        return Err(WindowsError::InvalidBootstrap);
1045    }
1046    Ok(nonce)
1047}
1048
1049fn last_os(operation: &'static str) -> WindowsError {
1050    // SAFETY: GetLastError has no preconditions.
1051    WindowsError::Os {
1052        operation,
1053        code: unsafe { GetLastError() },
1054    }
1055}
1056
1057/// Reports the native backend's enforced capability policy.
1058pub const fn status() -> BackendStatus {
1059    BackendStatus::Available
1060}
1061
1062#[cfg(test)]
1063mod tests {
1064    use super::*;
1065    use native_ipc_core::layout::{
1066        AcknowledgementRouteSpec, Endpoint, LayoutLimits, RegionSpec, RoleId,
1067    };
1068    use std::time::Duration;
1069    use windows_sys::Win32::System::Memory::{PAGE_EXECUTE_READWRITE, VirtualProtect};
1070
1071    fn topology() -> (RegionSetLayout, RoleId, RoleId) {
1072        let producer = RoleId::new(1).unwrap();
1073        let peer = RoleId::new(2).unwrap();
1074        let specs = [
1075            RegionSpec {
1076                role: producer,
1077                writer: Endpoint::Initiator,
1078                slot_count: 1,
1079                payload_bytes: 32,
1080                acknowledgement_count: 1,
1081            },
1082            RegionSpec {
1083                role: peer,
1084                writer: Endpoint::Responder,
1085                slot_count: 1,
1086                payload_bytes: 32,
1087                acknowledgement_count: 1,
1088            },
1089        ];
1090        let routes = [
1091            AcknowledgementRouteSpec {
1092                owner: peer,
1093                target: producer,
1094                slot_index: 0,
1095                cell_index: 0,
1096            },
1097            AcknowledgementRouteSpec {
1098                owner: producer,
1099                target: peer,
1100                slot_index: 0,
1101                cell_index: 0,
1102            },
1103        ];
1104        let topology = RegionSetLayout::calculate(
1105            [7; 32],
1106            23,
1107            &specs,
1108            &routes,
1109            LayoutLimits {
1110                maximum_mapping_size: 1 << 20,
1111                maximum_slot_count: 2,
1112                maximum_acknowledgement_count: 2,
1113                maximum_payload_bytes: 64,
1114            },
1115        )
1116        .unwrap();
1117        (topology, producer, peer)
1118    }
1119
1120    fn expected(topology: &RegionSetLayout, role: RoleId, len: usize) -> ValidationExpectations {
1121        let region = topology.region(role).unwrap();
1122        ValidationExpectations {
1123            schema_id: [7; 32],
1124            generation: 23,
1125            role,
1126            writer: region.writer(),
1127            maximum_mapping_size: len as u64,
1128        }
1129    }
1130
1131    #[test]
1132    fn nonce_is_nonzero_and_job_is_constructible() {
1133        assert_ne!(session_nonce().unwrap(), [0; 32]);
1134        let _job = ChildJob::new().unwrap();
1135    }
1136
1137    #[test]
1138    fn unnamed_section_is_page_rounded_and_zeroed() {
1139        let region = QuiescentRegion::new(37).unwrap();
1140        assert!(region.len() >= 37);
1141        assert!(region.as_bytes().iter().all(|byte| *byte == 0));
1142        let mut prior = 0;
1143        // SAFETY: the complete live view and protection output are valid.
1144        assert_eq!(
1145            unsafe {
1146                VirtualProtect(
1147                    region.view.base.as_ptr().cast(),
1148                    region.len(),
1149                    PAGE_EXECUTE_READWRITE,
1150                    &mut prior,
1151                )
1152            },
1153            0
1154        );
1155    }
1156
1157    #[test]
1158    fn read_only_duplicate_rejects_writable_mapping() {
1159        let region = QuiescentRegion::new(4096).unwrap();
1160        let duplicate = duplicate_to(
1161            region.section.0,
1162            unsafe { GetCurrentProcess() },
1163            FILE_MAP_READ,
1164        )
1165        .unwrap();
1166        let duplicate = OwnedHandle::new(duplicate.0 as HANDLE).unwrap();
1167        // SAFETY: exact read-only section handle is live; the denied result is not owned.
1168        let denied = unsafe { MapViewOfFile(duplicate.0, FILE_MAP_WRITE, 0, 0, region.len()) };
1169        assert!(denied.Value.is_null());
1170    }
1171
1172    #[test]
1173    fn spawned_helper_is_pid_authenticated_and_job_owned() {
1174        let (topology, producer, peer) = topology();
1175        let producer_layout = topology.region(producer).unwrap();
1176        let mut producer_region =
1177            QuiescentRegion::new(producer_layout.total_size() as usize).unwrap();
1178        producer_layout
1179            .encode_into(producer_region.as_bytes_mut())
1180            .unwrap();
1181        let producer_expected = expected(&topology, producer, producer_region.len());
1182        let prepared_producer = producer_region
1183            .prepare_local_writer(producer_expected, topology.clone())
1184            .unwrap();
1185        let peer_layout = topology.region(peer).unwrap();
1186        let mut peer_region = QuiescentRegion::new(peer_layout.total_size() as usize).unwrap();
1187        peer_layout.encode_into(peer_region.as_bytes_mut()).unwrap();
1188        let peer_expected = expected(&topology, peer, peer_region.len());
1189        let mut prepared_peer = peer_region
1190            .prepare_remote_writer(peer_expected, topology.clone())
1191            .unwrap();
1192        let executable = std::env::current_exe().unwrap();
1193        let arguments = [
1194            OsString::from("--exact"),
1195            OsString::from("windows::tests::spawned_helper_entry"),
1196            OsString::from("--ignored"),
1197            OsString::from("--nocapture"),
1198        ];
1199        let child = ChildSession::spawn(&executable, &arguments).unwrap();
1200        assert_ne!(child.pid(), unsafe { GetCurrentProcessId() });
1201        // SAFETY: held process was authenticated by exact kernel pipe PID.
1202        let reader_handle = unsafe {
1203            prepared_producer
1204                .duplicate_reader_to(child.process_handle())
1205                .unwrap()
1206        };
1207        // SAFETY: same held process; this is the only remote writable duplicate.
1208        let writer_handle = unsafe {
1209            prepared_peer
1210                .duplicate_writer_to(child.process_handle())
1211                .unwrap()
1212        };
1213        assert!(matches!(
1214            // SAFETY: the call is intentionally repeated against the same held process
1215            // to verify that the typestate rejects a second writable duplicate.
1216            unsafe { prepared_peer.duplicate_writer_to(child.process_handle()) },
1217            Err(WindowsError::CapabilityAlreadyTransferred)
1218        ));
1219        child
1220            .send_capabilities(
1221                reader_handle,
1222                prepared_producer.view.len,
1223                writer_handle,
1224                prepared_peer.view.len,
1225            )
1226            .unwrap();
1227        let mut writer = prepared_producer.bind().unwrap();
1228        let reader = prepared_peer.bind_reader().unwrap();
1229        child.wait_ready().unwrap();
1230        writer
1231            .publish(0, 1, None, b"cross-process-windows")
1232            .unwrap();
1233        for _ in 0..10_000 {
1234            if let Ok(payload) = reader.copy_payload(0, 1) {
1235                assert_eq!(payload, b"child-windows-writer");
1236                child.wait().unwrap();
1237                return;
1238            }
1239            std::thread::sleep(Duration::from_millis(1));
1240        }
1241        panic!("child never published payload");
1242    }
1243
1244    #[test]
1245    fn helper_exit_before_connect_is_bounded() {
1246        let executable = std::env::current_exe().unwrap();
1247        let arguments = [
1248            OsString::from("--exact"),
1249            OsString::from("windows::tests::exit_before_connect_entry"),
1250            OsString::from("--ignored"),
1251        ];
1252        let result = ChildSession::spawn(&executable, &arguments);
1253        assert!(matches!(
1254            result,
1255            Err(WindowsError::ChildExit(0) | WindowsError::Os { .. })
1256        ));
1257    }
1258
1259    #[test]
1260    fn helper_stall_before_auth_is_bounded() {
1261        let executable = std::env::current_exe().unwrap();
1262        let arguments = [
1263            OsString::from("--exact"),
1264            OsString::from("windows::tests::stall_before_auth_entry"),
1265            OsString::from("--ignored"),
1266        ];
1267        let result = ChildSession::spawn(&executable, &arguments);
1268        assert!(matches!(result, Err(WindowsError::TimedOut("pipe read"))));
1269    }
1270
1271    #[test]
1272    #[ignore = "spawned only by the exact lifecycle test"]
1273    fn spawned_helper_entry() {
1274        let (topology, producer, peer) = topology();
1275        let channel = connect_spawned_helper().unwrap();
1276        assert_ne!(channel.parent_pid(), unsafe { GetCurrentProcessId() });
1277        let (reader_handle, reader_len, writer_handle, writer_len) =
1278            channel.receive_capabilities().unwrap();
1279        // SAFETY: exact handles arrived from authenticated parent on private pipe.
1280        let reader = unsafe {
1281            import_reader(
1282                reader_handle.0,
1283                reader_len,
1284                expected(&topology, producer, reader_len),
1285                topology.clone(),
1286            )
1287            .unwrap()
1288        };
1289        // SAFETY: manifest designates this exact handle as the sole writer.
1290        let mut writer = unsafe {
1291            import_writer(
1292                writer_handle.0,
1293                writer_len,
1294                expected(&topology, peer, writer_len),
1295                topology,
1296            )
1297            .unwrap()
1298        };
1299        channel.signal_ready().unwrap();
1300        for _ in 0..10_000 {
1301            if let Ok(payload) = reader.copy_payload(0, 1) {
1302                assert_eq!(payload, b"cross-process-windows");
1303                writer.publish(0, 1, None, b"child-windows-writer").unwrap();
1304                return;
1305            }
1306            std::thread::sleep(Duration::from_millis(1));
1307        }
1308        panic!("parent never published payload");
1309    }
1310
1311    #[test]
1312    #[ignore = "spawned only by the exit-before-connect lifecycle test"]
1313    fn exit_before_connect_entry() {}
1314
1315    #[test]
1316    #[ignore = "spawned only by the stalled-auth lifecycle test"]
1317    fn stall_before_auth_entry() {
1318        let name = wide_null(&std::env::var_os(PIPE_ENV).unwrap());
1319        // SAFETY: terminated private pipe name and existing-only open are valid.
1320        let pipe = unsafe {
1321            CreateFileW(
1322                name.as_ptr(),
1323                GENERIC_READ | GENERIC_WRITE,
1324                0,
1325                std::ptr::null(),
1326                OPEN_EXISTING,
1327                0,
1328                std::ptr::null_mut(),
1329            )
1330        };
1331        let _pipe = OwnedHandle::new(pipe).unwrap();
1332        std::thread::sleep(Duration::from_secs(5));
1333    }
1334}