Skip to main content

running_process/broker/server/handoff/
windows.rs

1//! Windows `DuplicateHandle` handoff transport model.
2//!
3//! This module preserves the public 4.x transport model and maps the selected
4//! platform IPC primitive into the existing silent reconnect fallback policy.
5//! Native `DuplicateHandle` mechanics live in the platform package.
6
7use super::{
8    HandoffAttemptDecision, HandoffAttemptFailure, HandoffFallbackDecision, HandoffFallbackReason,
9    HandoffToken,
10};
11
12/// Whether this build target can eventually use the Windows handoff transport.
13pub const DUPLICATE_HANDLE_TRANSPORT_SUPPORTED: bool =
14    running_process_platform_internal::LEGACY_DUPLICATE_HANDLE_TRANSPORT_SUPPORTED;
15
16/// Opaque raw Windows handle value held by the broker or duplicated into a backend.
17#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
18pub struct WindowsHandleValue(usize);
19
20impl WindowsHandleValue {
21    /// Build an opaque handle value for transport bookkeeping.
22    pub fn new(value: usize) -> Self {
23        Self(value)
24    }
25
26    /// Return the raw opaque handle value.
27    pub fn get(self) -> usize {
28        self.0
29    }
30}
31
32/// Inputs for one future `DuplicateHandle` attempt.
33#[derive(Clone, Debug, PartialEq, Eq)]
34pub struct DuplicateHandleAttempt {
35    /// Broker-owned pipe handle to duplicate.
36    pub pipe_handle: WindowsHandleValue,
37    /// Backend process ID that should receive the duplicated handle.
38    pub backend_pid: u32,
39    /// One-time token associated with this handoff attempt.
40    pub handoff_token: HandoffToken,
41}
42
43impl DuplicateHandleAttempt {
44    /// Build typed inputs for one `DuplicateHandle` attempt.
45    pub fn new(
46        pipe_handle: WindowsHandleValue,
47        backend_pid: u32,
48        handoff_token: HandoffToken,
49    ) -> Self {
50        Self {
51            pipe_handle,
52            backend_pid,
53            handoff_token,
54        }
55    }
56}
57
58/// Successful `DuplicateHandle` outcome once real handle passing is wired.
59#[derive(Clone, Debug, PartialEq, Eq)]
60pub struct DuplicateHandleSuccess {
61    /// Handle value duplicated into the backend process.
62    pub duplicated_handle: WindowsHandleValue,
63    /// Backend process ID that received the duplicated handle.
64    pub backend_pid: u32,
65    /// One-time token paired with the duplicated handle.
66    pub handoff_token: HandoffToken,
67}
68
69impl DuplicateHandleSuccess {
70    /// Build a typed successful handoff result.
71    pub fn new(
72        duplicated_handle: WindowsHandleValue,
73        backend_pid: u32,
74        handoff_token: HandoffToken,
75    ) -> Self {
76        Self {
77            duplicated_handle,
78            backend_pid,
79            handoff_token,
80        }
81    }
82}
83
84/// Result returned by the future Windows transport.
85pub type DuplicateHandleResult = Result<DuplicateHandleSuccess, DuplicateHandleError>;
86
87/// Try to duplicate the broker-held pipe handle into the backend process.
88///
89/// The returned handle value is valid in the backend process handle table.
90/// Callers must still deliver the paired [`HandoffToken`] over the
91/// broker-to-backend control channel and wait for backend acknowledgement
92/// before reporting handoff success to the client.
93pub fn try_duplicate_handle(attempt: &DuplicateHandleAttempt) -> DuplicateHandleResult {
94    let duplicated = running_process_platform_internal::legacy_duplicate_handle(
95        attempt.pipe_handle.get(),
96        attempt.backend_pid,
97    )
98    .map_err(|error| legacy_error(attempt.backend_pid, error))?;
99    Ok(DuplicateHandleSuccess::new(
100        WindowsHandleValue::new(duplicated),
101        attempt.backend_pid,
102        attempt.handoff_token,
103    ))
104}
105
106fn legacy_error(
107    backend_pid: u32,
108    error: running_process_platform_internal::LegacyHandoffError,
109) -> DuplicateHandleError {
110    use running_process_platform_internal::platform::ipc::HandoffTransferErrorKind;
111
112    match error.kind() {
113        HandoffTransferErrorKind::Unsupported => DuplicateHandleError::UnsupportedPlatform,
114        HandoffTransferErrorKind::PermissionDenied => {
115            DuplicateHandleError::PermissionDenied { backend_pid }
116        }
117        HandoffTransferErrorKind::BackendUnavailable => {
118            DuplicateHandleError::CannotOpenBackend { backend_pid }
119        }
120        HandoffTransferErrorKind::WouldBlock | HandoffTransferErrorKind::Failed => {
121            DuplicateHandleError::DuplicateFailed {
122                backend_pid,
123                raw_os_error: error.raw_os_error(),
124            }
125        }
126    }
127}
128
129/// Failure from a future `DuplicateHandle` handoff attempt.
130#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
131pub enum DuplicateHandleError {
132    /// The current target cannot use the Windows handoff transport.
133    #[error("DuplicateHandle handoff transport is unsupported on this platform")]
134    UnsupportedPlatform,
135    /// Opening the backend process for `PROCESS_DUP_HANDLE` failed.
136    #[error("cannot open backend process {backend_pid} for DuplicateHandle")]
137    CannotOpenBackend {
138        /// Backend process ID that could not be opened.
139        backend_pid: u32,
140    },
141    /// The platform denied handle duplication.
142    #[error("permission denied duplicating handle into backend process {backend_pid}")]
143    PermissionDenied {
144        /// Backend process ID targeted by the handoff.
145        backend_pid: u32,
146    },
147    /// `DuplicateHandle` failed after the backend process was opened.
148    #[error("DuplicateHandle failed for backend process {backend_pid}")]
149    DuplicateFailed {
150        /// Backend process ID targeted by the handoff.
151        backend_pid: u32,
152        /// Raw Windows error code returned by the platform, when available.
153        raw_os_error: Option<i32>,
154    },
155    /// The broker and backend trust or integrity levels are incompatible.
156    #[error("integrity mismatch duplicating handle into backend process {backend_pid}")]
157    IntegrityMismatch {
158        /// Backend process ID targeted by the handoff.
159        backend_pid: u32,
160    },
161    /// The backend did not acknowledge the duplicated handle before the deadline.
162    #[error("backend process {backend_pid} did not acknowledge duplicated handle")]
163    BackendAckTimeout {
164        /// Backend process ID targeted by the handoff.
165        backend_pid: u32,
166    },
167}
168
169impl DuplicateHandleError {
170    /// Return the existing attempt-failure classification, when this was a real attempt.
171    pub fn attempt_failure(&self) -> Option<HandoffAttemptFailure> {
172        match self {
173            Self::UnsupportedPlatform => None,
174            Self::CannotOpenBackend { .. }
175            | Self::PermissionDenied { .. }
176            | Self::DuplicateFailed { .. } => Some(HandoffAttemptFailure::PermissionDenied),
177            Self::IntegrityMismatch { .. } => Some(HandoffAttemptFailure::IntegrityMismatch),
178            Self::BackendAckTimeout { .. } => Some(HandoffAttemptFailure::BackendAckTimeout),
179        }
180    }
181
182    /// Map this transport failure into the existing fallback reason vocabulary.
183    pub fn fallback_reason(&self) -> HandoffFallbackReason {
184        match self.attempt_failure() {
185            Some(failure) => failure.into(),
186            None => HandoffFallbackReason::ServicePolicyDisabled,
187        }
188    }
189
190    /// Return the silent reconnect fallback for this transport failure.
191    pub fn fallback_decision(&self) -> HandoffFallbackDecision {
192        HandoffFallbackDecision::new(self.fallback_reason())
193    }
194
195    /// Return the full attempt decision for callers that operate on broker decisions.
196    pub fn fallback_attempt_decision(&self) -> HandoffAttemptDecision {
197        HandoffAttemptDecision::FallbackToReconnect(self.fallback_decision())
198    }
199
200    /// Return true when this error is safe to hide behind reconnect fallback.
201    pub fn is_fallback_safe(&self) -> bool {
202        let fallback = self.fallback_decision();
203        fallback.uses_backend_reconnect() && !fallback.sends_client_error()
204    }
205}