use std::collections::HashMap;
use std::iter;
use std::mem::size_of;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex, OnceLock};
use std::time::{Duration, Instant};
use windows::Win32::Foundation::{
CloseHandle, ERROR_BROKEN_PIPE, ERROR_IO_PENDING, ERROR_NO_DATA, ERROR_PIPE_BUSY,
ERROR_PIPE_CONNECTED, ERROR_PIPE_NOT_CONNECTED, ERROR_SEM_TIMEOUT, GetLastError, HANDLE,
HLOCAL, LocalFree, WAIT_OBJECT_0, WAIT_TIMEOUT, WIN32_ERROR,
};
use windows::Win32::Security::Authorization::{
ConvertSidToStringSidW, ConvertStringSecurityDescriptorToSecurityDescriptorW, SDDL_REVISION_1,
};
use windows::Win32::Security::{
GetTokenInformation, PSECURITY_DESCRIPTOR, SECURITY_ATTRIBUTES, TOKEN_QUERY, TOKEN_USER,
TokenUser,
};
use windows::Win32::Storage::FileSystem::{
CreateFileW, FILE_FLAG_FIRST_PIPE_INSTANCE, FILE_FLAG_OVERLAPPED, FILE_FLAGS_AND_ATTRIBUTES,
FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_SHARE_NONE, OPEN_EXISTING, PIPE_ACCESS_DUPLEX,
ReadFile, SECURITY_IDENTIFICATION, SECURITY_SQOS_PRESENT, WriteFile,
};
use windows::Win32::System::IO::{CancelIoEx, GetOverlappedResult, OVERLAPPED};
use windows::Win32::System::Pipes::{
ConnectNamedPipe, CreateNamedPipeW, PIPE_READMODE_BYTE, PIPE_REJECT_REMOTE_CLIENTS,
PIPE_TYPE_BYTE, PIPE_UNLIMITED_INSTANCES, PIPE_WAIT, WaitNamedPipeW,
};
use windows::Win32::System::Threading::{
CreateEventW, GetCurrentProcess, INFINITE, OpenProcessToken, WaitForSingleObject,
};
use windows::core::{PCWSTR, PWSTR};
use crate::pal::error::{PalError, PalErrorKind};
use crate::pal::ids::{ConnId, ListenerId};
use crate::pal::raw_handle::PipeHandle;
use crate::pal::transport::Transport;
use crate::protocol::{Message, decode_body, encode, payload_len_ok};
struct PipeTable {
listeners: HashMap<ListenerId, Listener>,
conns: HashMap<ConnId, Conn>,
}
struct Listener {
name: Vec<u16>,
pending: Arc<PipeHandle>,
}
struct Conn {
handle: Arc<PipeHandle>,
write: Arc<Mutex<()>>,
}
#[derive(Clone, Copy)]
struct Deadline {
started: Instant,
timeout: Duration,
}
impl Deadline {
fn after(timeout: Duration) -> Self {
Self {
started: Instant::now(),
timeout,
}
}
fn wait_millis(self) -> Result<u32, PalError> {
const MIN_FINITE_WAIT_MILLIS: u32 = 1;
const MAX_FINITE_WAIT_MILLIS: u32 = u32::MAX.saturating_sub(1);
let remaining = self.timeout.saturating_sub(self.started.elapsed());
if remaining.is_zero() {
return Err(PalError::new(PalErrorKind::Timeout));
}
Ok(u32::try_from(ceil_millis(remaining))
.unwrap_or(MAX_FINITE_WAIT_MILLIS)
.clamp(MIN_FINITE_WAIT_MILLIS, MAX_FINITE_WAIT_MILLIS))
}
}
fn ceil_millis(duration: Duration) -> u128 {
duration
.as_nanos()
.div_ceil(Duration::from_millis(1).as_nanos())
}
fn table() -> &'static Mutex<PipeTable> {
static TABLE: OnceLock<Mutex<PipeTable>> = OnceLock::new();
TABLE.get_or_init(|| {
Mutex::new(PipeTable {
listeners: HashMap::new(),
conns: HashMap::new(),
})
})
}
fn next_id() -> u64 {
static NEXT: AtomicU64 = AtomicU64::new(1);
NEXT.fetch_add(1, Ordering::Relaxed)
}
fn io_error_kind(err: WIN32_ERROR) -> PalErrorKind {
if err == ERROR_BROKEN_PIPE || err == ERROR_NO_DATA || err == ERROR_PIPE_NOT_CONNECTED {
PalErrorKind::Disconnected
} else {
PalErrorKind::Other
}
}
fn close(handle: HANDLE) {
if handle.is_invalid() {
return;
}
_ = unsafe { CloseHandle(handle) };
}
fn wide_z(s: &str) -> Vec<u16> {
s.encode_utf16().chain(iter::once(0)).collect()
}
const PIPE_BUFFER: u32 = 65_536;
struct UserPipeSecurity {
descriptor: PSECURITY_DESCRIPTOR,
attrs: SECURITY_ATTRIBUTES,
}
impl UserPipeSecurity {
fn new() -> Result<Self, PalError> {
let sid = current_user_sid_string()?;
let sddl = format!("D:P(A;;GA;;;{sid})");
let wide = wide_z(&sddl);
let mut descriptor = PSECURITY_DESCRIPTOR::default();
let converted = unsafe {
ConvertStringSecurityDescriptorToSecurityDescriptorW(
PCWSTR(wide.as_ptr()),
SDDL_REVISION_1,
&raw mut descriptor,
None,
)
};
converted.map_err(|error| PalError::with_source(PalErrorKind::Other, error))?;
let attrs = SECURITY_ATTRIBUTES {
nLength: u32::try_from(size_of::<SECURITY_ATTRIBUTES>())
.expect("SECURITY_ATTRIBUTES fits in u32"),
lpSecurityDescriptor: descriptor.0,
bInheritHandle: false.into(),
};
Ok(Self { descriptor, attrs })
}
}
impl Drop for UserPipeSecurity {
fn drop(&mut self) {
if !self.descriptor.0.is_null() {
_ = unsafe { LocalFree(Some(HLOCAL(self.descriptor.0))) };
self.descriptor = PSECURITY_DESCRIPTOR::default();
}
}
}
pub(crate) fn current_user_sid_string() -> Result<String, PalError> {
let mut token = HANDLE::default();
let process = unsafe { GetCurrentProcess() };
unsafe { OpenProcessToken(process, TOKEN_QUERY, &raw mut token) }
.map_err(|error| PalError::with_source(PalErrorKind::Other, error))?;
let mut len = 0_u32;
_ = unsafe { GetTokenInformation(token, TokenUser, None, 0, &raw mut len) };
let words = usize::try_from(len)
.expect("token info size fits in usize")
.div_ceil(size_of::<u64>());
let mut buf = vec![0_u64; words];
let byte_len = u32::try_from(buf.len().saturating_mul(size_of::<u64>()))
.expect("token info buffer fits in u32");
let queried = unsafe {
GetTokenInformation(
token,
TokenUser,
Some(buf.as_mut_ptr().cast()),
byte_len,
&raw mut len,
)
};
close(token);
queried.map_err(|error| PalError::with_source(PalErrorKind::Other, error))?;
let mut sid_str = PWSTR::null();
{
let user = unsafe { buf.as_ptr().cast::<TOKEN_USER>().as_ref_unchecked() };
unsafe { ConvertSidToStringSidW(user.User.Sid, &raw mut sid_str) }
.map_err(|error| PalError::with_source(PalErrorKind::Other, error))?;
}
let wide = unsafe { sid_str.as_wide() }.to_vec();
_ = unsafe { LocalFree(Some(HLOCAL(sid_str.0.cast()))) };
String::from_utf16(&wide).map_err(|error| PalError::with_source(PalErrorKind::Other, error))
}
fn create_instance(name: &[u16], first: bool) -> Result<HANDLE, PalError> {
let mut open_mode = PIPE_ACCESS_DUPLEX.0 | FILE_FLAG_OVERLAPPED.0;
if first {
open_mode |= FILE_FLAG_FIRST_PIPE_INSTANCE.0;
}
let security = UserPipeSecurity::new()?;
let handle = unsafe {
CreateNamedPipeW(
PCWSTR(name.as_ptr()),
FILE_FLAGS_AND_ATTRIBUTES(open_mode),
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS,
PIPE_UNLIMITED_INSTANCES,
PIPE_BUFFER,
PIPE_BUFFER,
0,
Some(&raw const security.attrs),
)
};
if handle.is_invalid() {
return Err(PalError::new(PalErrorKind::Other));
}
Ok(handle)
}
fn create_event() -> Result<HANDLE, PalError> {
unsafe { CreateEventW(None, true, false, None) }
.map_err(|error| PalError::with_source(PalErrorKind::Other, error))
}
fn wait_event(event: HANDLE, timeout_ms: u32) -> Result<(), PalError> {
let wait = unsafe { WaitForSingleObject(event, timeout_ms) };
if wait == WAIT_TIMEOUT {
return Err(PalError::new(PalErrorKind::Timeout));
}
if wait != WAIT_OBJECT_0 {
return Err(PalError::new(PalErrorKind::Other));
}
Ok(())
}
fn abandon_operation(handle: HANDLE, overlapped: &OVERLAPPED) {
_ = unsafe { CancelIoEx(handle, Some(&raw const *overlapped)) };
let mut transferred = 0_u32;
_ = unsafe { GetOverlappedResult(handle, &raw const *overlapped, &raw mut transferred, true) };
}
fn wait_pending(
handle: HANDLE,
event: HANDLE,
overlapped: &OVERLAPPED,
deadline: Option<Deadline>,
) -> Result<(), PalError> {
let wait_millis = match deadline.map_or(Ok(INFINITE), Deadline::wait_millis) {
Ok(wait_millis) => wait_millis,
Err(error) => {
abandon_operation(handle, overlapped);
return Err(error);
}
};
if let Err(error) = wait_event(event, wait_millis) {
abandon_operation(handle, overlapped);
return Err(error);
}
Ok(())
}
fn connect_instance(pipe: &PipeHandle, deadline: Option<Deadline>) -> Result<(), PalError> {
let handle = pipe.as_handle();
let event = create_event()?;
let mut overlapped = OVERLAPPED {
hEvent: event,
..Default::default()
};
let Some(result) =
pipe.issue(|handle| unsafe { ConnectNamedPipe(handle, Some(&raw mut overlapped)) })
else {
close(event);
return Err(PalError::new(PalErrorKind::Disconnected));
};
if result.is_ok() {
close(event);
return Ok(());
}
let err = {
unsafe { GetLastError() }
};
if err == ERROR_PIPE_CONNECTED {
close(event);
return Ok(());
}
if err != ERROR_IO_PENDING {
close(event);
return Err(PalError::new(PalErrorKind::Other));
}
if let Err(error) = wait_pending(handle, event, &overlapped, deadline) {
close(event);
return Err(error);
}
let mut transferred = 0_u32;
let completed =
unsafe { GetOverlappedResult(handle, &raw const overlapped, &raw mut transferred, true) };
close(event);
completed.map_err(|error| PalError::with_source(PalErrorKind::Disconnected, error))
}
fn read_exact_until(
pipe: &PipeHandle,
buf: &mut [u8],
deadline: Option<Deadline>,
) -> Result<(), PalError> {
let handle = pipe.as_handle();
let mut filled = 0_usize;
while filled < buf.len() {
let event = create_event()?;
let mut overlapped = OVERLAPPED {
hEvent: event,
..Default::default()
};
let mut transferred = 0_u32;
let dest = buf
.get_mut(filled..)
.ok_or_else(|| PalError::new(PalErrorKind::Other))?;
let Some(ok) = pipe.issue(|handle| unsafe {
ReadFile(
handle,
Some(dest),
Some(&raw mut transferred),
Some(&raw mut overlapped),
)
}) else {
close(event);
return Err(PalError::new(PalErrorKind::Disconnected));
};
if ok.is_err() {
let err = {
unsafe { GetLastError() }
};
if err != ERROR_IO_PENDING {
close(event);
return Err(PalError::new(io_error_kind(err)));
}
if let Err(error) = wait_pending(handle, event, &overlapped, deadline) {
close(event);
return Err(error);
}
if unsafe {
GetOverlappedResult(handle, &raw const overlapped, &raw mut transferred, true)
}
.is_err()
{
let err = {
unsafe { GetLastError() }
};
close(event);
return Err(PalError::new(io_error_kind(err)));
}
}
close(event);
if transferred == 0 {
return Err(PalError::new(PalErrorKind::Disconnected));
}
filled = filled
.checked_add(transferred as usize)
.ok_or_else(|| PalError::new(PalErrorKind::Other))?;
}
Ok(())
}
fn write_all(pipe: &PipeHandle, mut buf: &[u8]) -> Result<(), PalError> {
let handle = pipe.as_handle();
while !buf.is_empty() {
let event = create_event()?;
let mut overlapped = OVERLAPPED {
hEvent: event,
..Default::default()
};
let mut transferred = 0_u32;
let Some(ok) = pipe.issue(|handle| unsafe {
WriteFile(
handle,
Some(buf),
Some(&raw mut transferred),
Some(&raw mut overlapped),
)
}) else {
close(event);
return Err(PalError::new(PalErrorKind::Other));
};
if ok.is_err() {
let err = {
unsafe { GetLastError() }
};
if err != ERROR_IO_PENDING {
close(event);
return Err(PalError::new(PalErrorKind::Other));
}
if let Err(error) = wait_event(event, INFINITE) {
abandon_operation(handle, &overlapped);
close(event);
return Err(error);
}
if unsafe {
GetOverlappedResult(handle, &raw const overlapped, &raw mut transferred, true)
}
.is_err()
{
let err = {
unsafe { GetLastError() }
};
close(event);
return Err(PalError::new(io_error_kind(err)));
}
}
close(event);
if transferred == 0 {
return Err(PalError::new(PalErrorKind::Other));
}
buf = buf
.get(transferred as usize..)
.ok_or_else(|| PalError::new(PalErrorKind::Other))?;
}
Ok(())
}
fn conn_handle(conn: ConnId) -> Result<Arc<PipeHandle>, PalError> {
table()
.lock()
.expect("the pipe table is only inserted into and looked up, never held across a panic")
.conns
.get(&conn)
.map(|conn| Arc::clone(&conn.handle))
.ok_or_else(|| PalError::new(PalErrorKind::NotFound))
}
fn conn_write(conn: ConnId) -> Result<(Arc<PipeHandle>, Arc<Mutex<()>>), PalError> {
table()
.lock()
.expect("the pipe table is only inserted into and looked up, never held across a panic")
.conns
.get(&conn)
.map(|conn| (Arc::clone(&conn.handle), Arc::clone(&conn.write)))
.ok_or_else(|| PalError::new(PalErrorKind::NotFound))
}
fn accept_connection(listener: ListenerId, timeout: Option<Duration>) -> Result<ConnId, PalError> {
let deadline = timeout.map(Deadline::after);
let (pending, name) = {
let table = table().lock().expect(
"the pipe table is only inserted into and looked up, never held across a panic",
);
let listener = table
.listeners
.get(&listener)
.ok_or_else(|| PalError::new(PalErrorKind::NotFound))?;
(Arc::clone(&listener.pending), listener.name.clone())
};
let connected = connect_instance(&pending, deadline);
let mut table = table()
.lock()
.expect("the pipe table is only inserted into and looked up, never held across a panic");
if !table.listeners.contains_key(&listener) {
return Err(PalError::new(PalErrorKind::Disconnected));
}
connected?;
let id = ConnId(next_id());
table.conns.insert(
id,
Conn {
handle: pending,
write: Arc::new(Mutex::new(())),
},
);
let next = match create_instance(&name, false) {
Ok(next) => next,
Err(error) => {
table.conns.remove(&id);
return Err(error);
}
};
let Some(listener_state) = table.listeners.get_mut(&listener) else {
close(next);
table.conns.remove(&id);
return Err(PalError::new(PalErrorKind::Disconnected));
};
listener_state.pending = PipeHandle::new(next);
Ok(id)
}
fn recv_message(conn: ConnId, timeout: Option<Duration>) -> Result<Message, PalError> {
let deadline = timeout.map(Deadline::after);
let handle = conn_handle(conn)?;
let mut header = [0_u8; 4];
read_exact_until(&handle, &mut header, deadline)?;
let len = u32::from_le_bytes(header);
if !payload_len_ok(len) {
return Err(PalError::new(PalErrorKind::Other));
}
let mut kind = [0_u8; 1];
read_exact_until(&handle, &mut kind, deadline)?;
let body_len = (len as usize)
.checked_sub(kind.len())
.ok_or_else(|| PalError::new(PalErrorKind::Other))?;
let mut body = vec![0_u8; body_len];
read_exact_until(&handle, &mut body, deadline)?;
decode_body(kind[0], body).map_err(|_error| PalError::new(PalErrorKind::Other))
}
#[derive(Debug, Default)]
pub(crate) struct BuildTargetTransport;
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg_attr(test, mutants::skip)]
impl Transport for BuildTargetTransport {
fn listen(&self, name: &str) -> Result<ListenerId, PalError> {
let name = wide_z(name);
let pending = create_instance(&name, true)?;
let id = ListenerId(next_id());
table()
.lock()
.expect("the pipe table is only inserted into and looked up, never held across a panic")
.listeners
.insert(
id,
Listener {
name,
pending: PipeHandle::new(pending),
},
);
Ok(id)
}
fn accept(&self, listener: ListenerId) -> Result<ConnId, PalError> {
accept_connection(listener, None)
}
fn accept_timeout(&self, listener: ListenerId, timeout: Duration) -> Result<ConnId, PalError> {
accept_connection(listener, Some(timeout))
}
fn connect(&self, name: &str, timeout: Duration) -> Result<ConnId, PalError> {
let name = wide_z(name);
let access = FILE_GENERIC_READ.0 | FILE_GENERIC_WRITE.0;
let started = Instant::now();
loop {
let remaining = timeout.saturating_sub(started.elapsed());
if remaining.is_zero() {
return Err(PalError::new(PalErrorKind::Timeout));
}
let timeout_ms = u32::try_from(ceil_millis(remaining))
.unwrap_or(u32::MAX)
.clamp(2, u32::MAX - 1);
let ready = unsafe { WaitNamedPipeW(PCWSTR(name.as_ptr()), timeout_ms) };
if !ready.as_bool() {
let err = unsafe { GetLastError() };
if err == ERROR_SEM_TIMEOUT {
continue;
}
return Err(PalError::new(PalErrorKind::NotFound));
}
let handle = unsafe {
CreateFileW(
PCWSTR(name.as_ptr()),
access,
FILE_SHARE_NONE,
None,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED | SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION,
None,
)
};
let handle = match handle {
Ok(handle) => handle,
Err(_error) => {
let err = {
unsafe { GetLastError() }
};
if err != ERROR_PIPE_BUSY {
return Err(PalError::new(PalErrorKind::NotFound));
}
continue;
}
};
let id = ConnId(next_id());
table()
.lock()
.expect(
"the pipe table is only inserted into and looked up, never held across a panic",
)
.conns
.insert(
id,
Conn {
handle: PipeHandle::new(handle),
write: Arc::new(Mutex::new(())),
},
);
return Ok(id);
}
}
fn send(&self, conn: ConnId, message: &Message) -> Result<(), PalError> {
let frame = encode(message);
let (handle, write) = conn_write(conn)?;
let _guard = write.lock().expect(
"a pipe write holds the lock only across the write itself, which does not panic",
);
write_all(&handle, &frame)
}
fn recv(&self, conn: ConnId) -> Result<Message, PalError> {
recv_message(conn, None)
}
fn recv_timeout(&self, conn: ConnId, timeout: Duration) -> Result<Message, PalError> {
recv_message(conn, Some(timeout))
}
fn disconnect(&self, conn: ConnId) {
let removed = table()
.lock()
.expect("the pipe table is only inserted into and looked up, never held across a panic")
.conns
.remove(&conn);
if let Some(conn) = removed {
conn.handle.cancel();
}
}
fn close_listener(&self, listener: ListenerId) {
let removed = table()
.lock()
.expect("the pipe table is only inserted into and looked up, never held across a panic")
.listeners
.remove(&listener);
if let Some(listener) = removed {
listener.pending.cancel();
}
}
fn pipe_name(&self, nonce: &str) -> String {
format!(r"\\.\pipe\dure-{nonce}")
}
}