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;
49use crate::protocol::{
50    CONTROL_FRAME_LEN, ManifestEntry, PeerAccess, TransferManifest, TransferProvenance,
51    mint_channel_id,
52};
53
54/// Windows section, bootstrap, lifecycle, or binding failure.
55#[derive(Debug)]
56pub enum WindowsError {
57    /// Win32 API failed with a captured `GetLastError` value.
58    Os {
59        /// Bounded Win32 operation name.
60        operation: &'static str,
61        /// Captured `GetLastError` value.
62        code: u32,
63    },
64    /// Mapping size is zero or cannot be page-rounded.
65    InvalidSize(usize),
66    /// Named-pipe peer PID differs from the held expected process.
67    WrongPeer,
68    /// Received or duplicated handle is invalid for this process.
69    InvalidHandle,
70    /// Quiescent layout validation failed.
71    Layout(native_ipc_core::layout::LayoutError),
72    /// Audited core binding failed.
73    Binding(BindingError),
74    /// Bootstrap environment or authenticated handshake was malformed.
75    InvalidBootstrap,
76    /// A sole-writer capability was already duplicated from this preparation.
77    CapabilityAlreadyTransferred,
78    /// A bounded bootstrap or lifecycle operation reached its deadline.
79    TimedOut(&'static str),
80    /// The exact helper exited unsuccessfully.
81    ChildExit(u32),
82    /// A pending value came from another channel or transfer transaction.
83    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
103/// Generates a nonzero 256-bit bootstrap nonce from the system RNG.
104pub fn session_nonce() -> Result<[u8; 32], WindowsError> {
105    let mut nonce = [0_u8; 32];
106    // SAFETY: output buffer is valid; null algorithm selects system-preferred RNG.
107    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
122/// Verifies the connected client of a private named-pipe server instance.
123///
124/// # Safety
125///
126/// `pipe` must be a live connected named-pipe server handle owned by the caller.
127pub unsafe fn authenticate_pipe_client(
128    pipe: HANDLE,
129    expected_pid: u32,
130) -> Result<(), WindowsError> {
131    let mut actual = 0;
132    // SAFETY: caller supplies a live pipe and output pointer is valid.
133    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
143/// Verifies the connected server of a private named-pipe client instance.
144///
145/// # Safety
146///
147/// `pipe` must be a live connected named-pipe client handle owned by the caller.
148pub unsafe fn authenticate_pipe_server(
149    pipe: HANDLE,
150    expected_pid: u32,
151) -> Result<(), WindowsError> {
152    let mut actual = 0;
153    // SAFETY: caller supplies a live pipe and output pointer is valid.
154    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
164/// Quiescent unnamed paging-file section and exclusive initialization view.
165pub struct QuiescentRegion {
166    section: OwnedHandle,
167    view: View,
168    logical_len: usize,
169}
170
171impl QuiescentRegion {
172    /// Allocates a page-rounded unnamed, non-executable section.
173    pub fn new(logical_len: usize) -> Result<Self, WindowsError> {
174        let len = page_align(logical_len)?;
175        let size = len as u64;
176        // SAFETY: paging-file sentinel, null security/name, and checked size are valid.
177        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        // SAFETY: newly created unnamed section view is exclusive and writable.
190        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    /// Exact page-rounded capability size.
198    pub const fn len(&self) -> usize {
199        self.view.len
200    }
201    /// Returns whether the capability is empty (always false for valid values).
202    pub const fn is_empty(&self) -> bool {
203        false
204    }
205    /// Requested logical layout length.
206    pub const fn logical_len(&self) -> usize {
207        self.logical_len
208    }
209    /// Full quiescent initialization range.
210    pub fn as_bytes(&self) -> &[u8] {
211        // SAFETY: no duplicated handle or second view exists in this typestate.
212        unsafe { std::slice::from_raw_parts(self.view.base.as_ptr(), self.view.len) }
213    }
214    /// Mutable full quiescent initialization range.
215    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
216        // SAFETY: `&mut self` and typestate provide exclusivity.
217        unsafe { std::slice::from_raw_parts_mut(self.view.base.as_ptr(), self.view.len) }
218    }
219
220    /// Validates a future local-writer region before attenuated duplication.
221    pub fn prepare_local_writer(
222        self,
223        expected: ValidationExpectations,
224        topology: RegionSetLayout,
225    ) -> Result<PreparedLocalWriter, WindowsError> {
226        // SAFETY: section is quiescent and complete capability range is borrowed.
227        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    /// Validates a future remote-writer region before remapping local read-only.
242    pub fn prepare_remote_writer(
243        self,
244        expected: ValidationExpectations,
245        topology: RegionSetLayout,
246    ) -> Result<PreparedRemoteWriter, WindowsError> {
247        // SAFETY: section is quiescent and complete capability range is borrowed.
248        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/// Target-process handle value produced by exact-rights duplication.
266#[derive(Clone, Copy, Debug, Eq, PartialEq)]
267pub struct RemoteHandle(pub usize);
268
269/// Local unique writer awaiting a read-only peer handle and READY barrier.
270///
271/// ```compile_fail
272/// use native_ipc_platform::windows::PreparedLocalWriter;
273/// fn publish_early(mut pending: PreparedLocalWriter) {
274///     pending.publish(0, 1, None, b"too early").unwrap();
275/// }
276/// ```
277pub struct PreparedLocalWriter {
278    section: OwnedHandle,
279    runtime: WriterRegion<WindowsWriterMapping>,
280    entry: ManifestEntry,
281    len: usize,
282    reader_duplicated: bool,
283}
284impl PreparedLocalWriter {
285    /// Duplicates exactly `FILE_MAP_READ` into a held authenticated target process.
286    ///
287    /// # Safety
288    ///
289    /// `target_process` must be the held live process authenticated by the pipe.
290    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
303/// Local read-only view awaiting the sole remote-writer handle and READY barrier.
304pub struct PreparedRemoteWriter {
305    section: OwnedHandle,
306    runtime: ReaderRegion<WindowsReaderMapping>,
307    entry: ManifestEntry,
308    len: usize,
309    writer_duplicated: bool,
310}
311impl PreparedRemoteWriter {
312    /// Duplicates exactly one `FILE_MAP_WRITE` handle into a held authenticated target.
313    ///
314    /// # Safety
315    ///
316    /// `target_process` must be the held live process authenticated by the pipe.
317    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
330/// Validated imported reader withheld until the creator acknowledges READY.
331///
332/// ```compile_fail
333/// use native_ipc_platform::windows::PendingImportedReader;
334/// fn read_early(pending: PendingImportedReader) {
335///     let _ = pending.copy_payload(0, 1);
336/// }
337/// ```
338pub struct PendingImportedReader {
339    runtime: ReaderRegion<WindowsReaderMapping>,
340    entry: ManifestEntry,
341    provenance: TransferProvenance,
342}
343
344/// Validated imported writer withheld until the creator acknowledges READY.
345pub struct PendingImportedWriter {
346    runtime: WriterRegion<WindowsWriterMapping>,
347    entry: ManifestEntry,
348    provenance: TransferProvenance,
349}
350
351/// Kill-on-last-handle Job Object used to contain an exact spawned helper tree.
352pub struct ChildJob(OwnedHandle);
353impl ChildJob {
354    /// Creates an unnamed non-inheritable kill-on-close job.
355    pub fn new() -> Result<Self, WindowsError> {
356        // SAFETY: null security/name create an unnamed non-inheritable job.
357        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        // SAFETY: buffer type/size match the requested information class.
362        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    /// Assigns a still-suspended exact child before any untrusted code runs.
376    ///
377    /// # Safety
378    ///
379    /// `process` must be the live suspended child handle returned by CreateProcess.
380    pub unsafe fn assign_suspended(&self, process: HANDLE) -> Result<(), WindowsError> {
381        // SAFETY: caller proves process handle/lifecycle; job handle is live.
382        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
447/// Parent-owned exact helper, private pipe, process handle, and kill-on-close job.
448pub 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    /// Creates a one-instance local pipe and launches the helper suspended.
461    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        // SAFETY: name is terminated; null security creates a non-inheritable handle.
466        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        // SAFETY: all UTF-16 buffers and output structures remain live; no handles inherit.
496        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        // SAFETY: CreateProcessW returned this exact child still suspended.
516        if let Err(error) = unsafe { job.assign_suspended(process.0) } {
517            // SAFETY: exact held child is still suspended.
518            let _ = unsafe { TerminateProcess(process.0, 127) };
519            return Err(error);
520        }
521        // SAFETY: thread is the exact suspended primary thread.
522        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        // SAFETY: the server pipe is connected and the expected PID is held live.
530        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    /// Exact live process handle used only for attenuated handle duplication.
560    pub const fn process_handle(&self) -> HANDLE {
561        self.process.0
562    }
563    /// Kernel-created child process ID authenticated on the private pipe.
564    pub const fn pid(&self) -> u32 {
565        self.pid
566    }
567    /// Sends the two exact-rights handle values and their complete mapped lengths.
568    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    /// Consumes prepared mappings after authenticated READY and sends COMMIT.
598    ///
599    /// This method owns capability duplication, the remote-handle cleanup
600    /// ledger, exact manifest transfer, READY validation, and COMMIT. It returns
601    /// `(local_writer, local_reader)` only after successful COMMIT.
602    ///
603    /// # Errors
604    ///
605    /// Returns an error for duplication, pipe, transcript, timeout, or process
606    /// failures. Ambiguous failure terminates and reaps the exact held child.
607    ///
608    /// ```compile_fail
609    /// use native_ipc_platform::windows::{
610    ///     ChildSession, PreparedLocalWriter, PreparedRemoteWriter,
611    /// };
612    /// fn commit_twice(
613    ///     session: &mut ChildSession,
614    ///     writer: PreparedLocalWriter,
615    ///     reader: PreparedRemoteWriter,
616    /// ) {
617    ///     let _ = session.commit_transfers(writer, reader);
618    ///     let _ = session.commit_transfers(writer, reader);
619    /// }
620    /// ```
621    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        // SAFETY: this session owns the exact authenticated live child handle.
651        let reader_handle = unsafe { writer.duplicate_reader_to(self.process.0)? };
652        // SAFETY: same held child; the preparation enforces one writer duplicate.
653        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            // SAFETY: this session owns the exact authenticated child handle.
677            let _ = unsafe { TerminateProcess(self.process.0, 127) };
678            // SAFETY: same held process; bounded wait completes cleanup.
679            let _ = unsafe { WaitForSingleObject(self.process.0, WAIT_MS) };
680            self.reaped = true;
681        }
682    }
683    /// Waits for a normal helper exit after protocol completion.
684    pub fn wait(mut self) -> Result<(), WindowsError> {
685        // SAFETY: process is held live for this session.
686        match unsafe { WaitForSingleObject(self.process.0, WAIT_MS) } {
687            WAIT_OBJECT_0 => {
688                let mut code = 0;
689                // SAFETY: held process is signaled and output pointer is valid.
690                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            // SAFETY: exact held child; job close remains the backstop for descendants.
709            let _ = unsafe { TerminateProcess(self.process.0, 127) };
710            let _ = unsafe { WaitForSingleObject(self.process.0, WAIT_MS) };
711        }
712    }
713}
714
715/// Connects a spawned helper from its authenticated bootstrap environment.
716pub 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    // SAFETY: terminated name; no sharing or inheritance and existing pipe only.
726    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    // SAFETY: connected client pipe and mode pointer are valid.
740    if unsafe { SetNamedPipeHandleState(pipe.0, &mode, std::ptr::null(), std::ptr::null()) } == 0 {
741        return Err(last_os("SetNamedPipeHandleState"));
742    }
743    // SAFETY: connected pipe client and exact expected parent from spawn environment.
744    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
772/// Authenticated child endpoint retained for the lifetime of imported capabilities.
773pub 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    /// Held authenticated parent PID.
784    pub const fn parent_pid(&self) -> u32 {
785        self.parent_pid
786    }
787    /// Raw pipe handle for a bounded manifest protocol owned by the caller.
788    pub const fn pipe_handle(&self) -> HANDLE {
789        self.pipe.0
790    }
791    /// Receives exact-rights handle values only after pipe PID authentication.
792    ///
793    /// The tuple is `(reader_handle, reader_len, writer_handle, writer_len)`.
794    /// Lengths are exact page-rounded capability sizes. Handles remain pending
795    /// and must be imported and passed to [`Self::commit_imports`].
796    ///
797    /// # Errors
798    ///
799    /// Returns an error for duplicate receipt, timeout, truncated or oversized
800    /// frames, invalid fixed-width values, or a malformed capability envelope.
801    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    /// Imports a duplicated read-only section handle for this open transaction.
841    ///
842    /// The pending value is bound to this channel and its current transfer
843    /// transaction; [`Self::commit_imports`] rejects values from any other
844    /// channel or transaction.
845    ///
846    /// # Safety
847    ///
848    /// `handle` must have arrived over this channel's authenticated bootstrap,
849    /// be owned by this process, have exactly the manifest rights, and not have
850    /// been previously closed.
851    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        // SAFETY: READY protocol keeps view quiescent; access is read-only.
861        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    /// Imports the sole duplicated writer handle for this open transaction.
874    ///
875    /// # Safety
876    ///
877    /// Same authenticated ownership requirements as [`Self::import_reader`],
878    /// plus the manifest/creator must guarantee no other writable handle or
879    /// view exists.
880    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        // SAFETY: READY protocol keeps the sole writer quiescent during validation.
890        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    /// Provenance stamp binding pending imports to the open transaction.
903    const fn pending_provenance(&self) -> TransferProvenance {
904        TransferProvenance::new(self.channel_id, self.next_transfer_id)
905    }
906
907    /// Signals validation, waits for COMMIT, then exposes imported capabilities.
908    ///
909    /// Returns `(imported_reader, imported_writer)` in manifest order.
910    ///
911    /// # Errors
912    ///
913    /// Returns an error if either pending value belongs to another channel or
914    /// transfer transaction, the imported entries do not match the capability
915    /// transcript, READY cannot be sent, or COMMIT is malformed or stale.
916    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
964/// Platform-minted unique writable unnamed-section view.
965pub struct WindowsWriterMapping {
966    view: View,
967}
968// SAFETY: constructors consume the full creator handle and retain the sole RW view.
969unsafe 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}
977/// Platform-minted read-only unnamed-section view.
978pub struct WindowsReaderMapping {
979    view: View,
980}
981// SAFETY: constructors map only FILE_MAP_READ and retain the view lifetime.
982unsafe 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        // SAFETY: this value uniquely owns a real non-pseudo handle.
1004        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        // SAFETY: section handle is live; access/offset/length are checked.
1015        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        // SAFETY: this object uniquely owns the mapped view.
1023        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    // SAFETY: source/current/target handles are live; no SAME_ACCESS option is used.
1034    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    // SAFETY: output pointer is valid.
1061    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    // SAFETY: callers use repr(C), fully initialized integer-only protocol records.
1148    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        // SAFETY: pipe is live, bytes are valid, and nonblocking operation is synchronous.
1161        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        // SAFETY: frame output range is valid and nonblocking operation is synchronous.
1198        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        // SAFETY: server pipe is nonblocking and no OVERLAPPED operation is requested.
1229        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        // SAFETY: exact child process handle is held throughout bootstrap.
1243        if unsafe { WaitForSingleObject(process, 0) } == WAIT_OBJECT_0 {
1244            let mut exit = 0;
1245            // SAFETY: process is signaled and output is valid.
1246            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    // SAFETY: GetLastError has no preconditions.
1290    WindowsError::Os {
1291        operation,
1292        code: unsafe { GetLastError() },
1293    }
1294}
1295
1296/// Reports the native backend's enforced capability policy.
1297pub 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        // SAFETY: the complete live view and protection output are valid.
1383        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        // SAFETY: exact read-only section handle is live; the denied result is not owned.
1407        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        // SAFETY: exact handles arrived from authenticated parent on private pipe.
1493        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        // SAFETY: manifest designates this exact handle as the sole writer.
1504        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        // SAFETY: terminated unique name; null security creates a private instance.
1531        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        // SAFETY: both duplicated handles are locally created, owned, and unused elsewhere.
1590        let reader = unsafe {
1591            first
1592                .import_reader(reader_dup.0, reader_len, reader_expected, topology.clone())
1593                .unwrap()
1594        };
1595        // SAFETY: the writable duplicate is the sole writer handle for its section.
1596        let writer = unsafe {
1597            first
1598                .import_writer(writer_dup.0, writer_len, writer_expected, topology)
1599                .unwrap()
1600        };
1601
1602        // Pending imports from the first channel must fail closed on the second
1603        // channel before any READY frame is written.
1604        assert!(matches!(
1605            second.commit_imports(reader, writer),
1606            Err(WindowsError::ForeignPending)
1607        ));
1608        // The mismatched transaction stays poisoned for later operations.
1609        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        // SAFETY: terminated private pipe name and existing-only open are valid.
1624        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}