use super::{
HandoffAttemptDecision, HandoffAttemptFailure, HandoffFallbackDecision, HandoffFallbackReason,
HandoffToken,
};
pub const DUPLICATE_HANDLE_TRANSPORT_SUPPORTED: bool = cfg!(windows);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct WindowsHandleValue(usize);
impl WindowsHandleValue {
pub fn new(value: usize) -> Self {
Self(value)
}
pub fn get(self) -> usize {
self.0
}
#[cfg(windows)]
fn from_handle(handle: windows_sys::Win32::Foundation::HANDLE) -> Self {
Self(handle as usize)
}
#[cfg(windows)]
fn as_handle(self) -> windows_sys::Win32::Foundation::HANDLE {
self.0 as windows_sys::Win32::Foundation::HANDLE
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DuplicateHandleAttempt {
pub pipe_handle: WindowsHandleValue,
pub backend_pid: u32,
pub handoff_token: HandoffToken,
}
impl DuplicateHandleAttempt {
pub fn new(
pipe_handle: WindowsHandleValue,
backend_pid: u32,
handoff_token: HandoffToken,
) -> Self {
Self {
pipe_handle,
backend_pid,
handoff_token,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DuplicateHandleSuccess {
pub duplicated_handle: WindowsHandleValue,
pub backend_pid: u32,
pub handoff_token: HandoffToken,
}
impl DuplicateHandleSuccess {
pub fn new(
duplicated_handle: WindowsHandleValue,
backend_pid: u32,
handoff_token: HandoffToken,
) -> Self {
Self {
duplicated_handle,
backend_pid,
handoff_token,
}
}
}
pub type DuplicateHandleResult = Result<DuplicateHandleSuccess, DuplicateHandleError>;
pub fn try_duplicate_handle(attempt: &DuplicateHandleAttempt) -> DuplicateHandleResult {
platform_try_duplicate_handle(attempt)
}
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
pub enum DuplicateHandleError {
#[error("DuplicateHandle handoff transport is unsupported on this platform")]
UnsupportedPlatform,
#[error("cannot open backend process {backend_pid} for DuplicateHandle")]
CannotOpenBackend {
backend_pid: u32,
},
#[error("permission denied duplicating handle into backend process {backend_pid}")]
PermissionDenied {
backend_pid: u32,
},
#[error("DuplicateHandle failed for backend process {backend_pid}")]
DuplicateFailed {
backend_pid: u32,
raw_os_error: Option<i32>,
},
#[error("integrity mismatch duplicating handle into backend process {backend_pid}")]
IntegrityMismatch {
backend_pid: u32,
},
#[error("backend process {backend_pid} did not acknowledge duplicated handle")]
BackendAckTimeout {
backend_pid: u32,
},
}
#[cfg(windows)]
fn platform_try_duplicate_handle(attempt: &DuplicateHandleAttempt) -> DuplicateHandleResult {
use windows_sys::Win32::Foundation::{
CloseHandle, DuplicateHandle, DUPLICATE_SAME_ACCESS, HANDLE,
};
use windows_sys::Win32::System::Threading::{
GetCurrentProcess, OpenProcess, PROCESS_DUP_HANDLE,
};
let backend_process = unsafe { OpenProcess(PROCESS_DUP_HANDLE, 0, attempt.backend_pid) };
if is_invalid_handle(backend_process) {
return Err(open_process_error(attempt.backend_pid));
}
let mut duplicated: HANDLE = std::ptr::null_mut();
let ok = unsafe {
DuplicateHandle(
GetCurrentProcess(),
attempt.pipe_handle.as_handle(),
backend_process,
&mut duplicated,
0,
0,
DUPLICATE_SAME_ACCESS,
)
};
let duplicate_error = std::io::Error::last_os_error();
unsafe {
CloseHandle(backend_process);
}
if ok == 0 || is_invalid_handle(duplicated) {
return Err(duplicate_handle_error(
attempt.backend_pid,
duplicate_error.raw_os_error(),
));
}
Ok(DuplicateHandleSuccess::new(
WindowsHandleValue::from_handle(duplicated),
attempt.backend_pid,
attempt.handoff_token,
))
}
#[cfg(not(windows))]
fn platform_try_duplicate_handle(_attempt: &DuplicateHandleAttempt) -> DuplicateHandleResult {
Err(DuplicateHandleError::UnsupportedPlatform)
}
#[cfg(windows)]
fn is_invalid_handle(handle: windows_sys::Win32::Foundation::HANDLE) -> bool {
use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
handle.is_null() || handle == INVALID_HANDLE_VALUE
}
#[cfg(windows)]
fn open_process_error(backend_pid: u32) -> DuplicateHandleError {
use windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED;
if std::io::Error::last_os_error().raw_os_error() == Some(ERROR_ACCESS_DENIED as i32) {
DuplicateHandleError::PermissionDenied { backend_pid }
} else {
DuplicateHandleError::CannotOpenBackend { backend_pid }
}
}
#[cfg(windows)]
fn duplicate_handle_error(backend_pid: u32, raw_os_error: Option<i32>) -> DuplicateHandleError {
use windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED;
if raw_os_error == Some(ERROR_ACCESS_DENIED as i32) {
DuplicateHandleError::PermissionDenied { backend_pid }
} else {
DuplicateHandleError::DuplicateFailed {
backend_pid,
raw_os_error,
}
}
}
#[cfg(all(test, windows))]
mod tests {
use super::*;
#[test]
fn duplicate_handle_into_current_process_returns_backend_owned_handle() {
use windows_sys::Win32::Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE};
use windows_sys::Win32::System::Threading::GetCurrentProcess;
let token = HandoffToken::from_bytes([7; 16]);
let attempt = DuplicateHandleAttempt::new(
WindowsHandleValue::new(unsafe { GetCurrentProcess() } as usize),
std::process::id(),
token,
);
let success = try_duplicate_handle(&attempt).unwrap();
assert_eq!(success.backend_pid, std::process::id());
assert_eq!(success.handoff_token, token);
assert_ne!(success.duplicated_handle.get(), 0);
assert_ne!(
success.duplicated_handle.get(),
INVALID_HANDLE_VALUE as usize
);
unsafe {
CloseHandle(success.duplicated_handle.get() as HANDLE);
}
}
#[test]
fn missing_backend_pid_maps_to_fallback_safe_error() {
let attempt = DuplicateHandleAttempt::new(
WindowsHandleValue::new(unsafe {
windows_sys::Win32::System::Threading::GetCurrentProcess()
} as usize),
u32::MAX,
HandoffToken::from_bytes([9; 16]),
);
let err = try_duplicate_handle(&attempt).unwrap_err();
assert!(matches!(
err,
DuplicateHandleError::CannotOpenBackend { .. }
| DuplicateHandleError::PermissionDenied { .. }
));
assert!(err.is_fallback_safe());
}
}
impl DuplicateHandleError {
pub fn attempt_failure(&self) -> Option<HandoffAttemptFailure> {
match self {
Self::UnsupportedPlatform => None,
Self::CannotOpenBackend { .. }
| Self::PermissionDenied { .. }
| Self::DuplicateFailed { .. } => Some(HandoffAttemptFailure::PermissionDenied),
Self::IntegrityMismatch { .. } => Some(HandoffAttemptFailure::IntegrityMismatch),
Self::BackendAckTimeout { .. } => Some(HandoffAttemptFailure::BackendAckTimeout),
}
}
pub fn fallback_reason(&self) -> HandoffFallbackReason {
match self.attempt_failure() {
Some(failure) => failure.into(),
None => HandoffFallbackReason::ServicePolicyDisabled,
}
}
pub fn fallback_decision(&self) -> HandoffFallbackDecision {
HandoffFallbackDecision::new(self.fallback_reason())
}
pub fn fallback_attempt_decision(&self) -> HandoffAttemptDecision {
HandoffAttemptDecision::FallbackToReconnect(self.fallback_decision())
}
pub fn is_fallback_safe(&self) -> bool {
let fallback = self.fallback_decision();
fallback.uses_backend_reconnect() && !fallback.sends_client_error()
}
}