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, 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, 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_payload, encode, payload_len_ok};
struct PipeTable {
listeners: HashMap<u64, Listener>,
conns: HashMap<u64, Conn>,
}
struct Listener {
name: Vec<u16>,
pending: Arc<PipeHandle>,
}
struct Conn {
handle: Arc<PipeHandle>,
write: Arc<Mutex<()>>,
}
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::new(PalErrorKind::Other))?;
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();
}
}
}
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::new(PalErrorKind::Other))?;
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::new(PalErrorKind::Other))?;
let mut sid_str = PWSTR::null();
{
let user = unsafe { &*buf.as_ptr().cast::<TOKEN_USER>() };
unsafe { ConvertSidToStringSidW(user.User.Sid, &raw mut sid_str) }
.map_err(|_error| PalError::new(PalErrorKind::Other))?;
}
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::new(PalErrorKind::Other))
}
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::new(PalErrorKind::Other))
}
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 connect_instance(handle: HANDLE) -> Result<(), PalError> {
let event = create_event()?;
let mut overlapped = OVERLAPPED {
hEvent: event,
..Default::default()
};
let result = unsafe { ConnectNamedPipe(handle, Some(&raw mut overlapped)) };
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_event(event, INFINITE) {
abandon_operation(handle, &overlapped);
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::new(PalErrorKind::Disconnected))
}
fn read_exact(handle: HANDLE, buf: &mut [u8]) -> Result<(), PalError> {
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 ok = unsafe {
ReadFile(
handle,
Some(dest),
Some(&raw mut transferred),
Some(&raw mut overlapped),
)
};
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_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::Disconnected));
}
filled = filled
.checked_add(transferred as usize)
.ok_or_else(|| PalError::new(PalErrorKind::Other))?;
}
Ok(())
}
fn write_all(handle: HANDLE, mut buf: &[u8]) -> Result<(), PalError> {
while !buf.is_empty() {
let event = create_event()?;
let mut overlapped = OVERLAPPED {
hEvent: event,
..Default::default()
};
let mut transferred = 0_u32;
let ok = unsafe {
WriteFile(
handle,
Some(buf),
Some(&raw mut transferred),
Some(&raw mut overlapped),
)
};
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("pipe table")
.conns
.get(&conn.0)
.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("pipe table")
.conns
.get(&conn.0)
.map(|conn| (Arc::clone(&conn.handle), Arc::clone(&conn.write)))
.ok_or_else(|| PalError::new(PalErrorKind::NotFound))
}
#[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 = next_id();
table().lock().expect("pipe table").listeners.insert(
id,
Listener {
name,
pending: PipeHandle::new(pending),
},
);
Ok(ListenerId(id))
}
fn accept(&self, listener: ListenerId) -> Result<ConnId, PalError> {
let (pending, name) = {
let table = table().lock().expect("pipe table");
let listener = table
.listeners
.get(&listener.0)
.ok_or_else(|| PalError::new(PalErrorKind::NotFound))?;
(Arc::clone(&listener.pending), listener.name.clone())
};
let connected = connect_instance(pending.as_handle());
let mut table = table().lock().expect("pipe table");
if !table.listeners.contains_key(&listener.0) {
return Err(PalError::new(PalErrorKind::Disconnected));
}
connected?;
let next = create_instance(&name, false)?;
{
let Some(listener_state) = table.listeners.get_mut(&listener.0) else {
close(next);
return Err(PalError::new(PalErrorKind::Disconnected));
};
listener_state.pending = PipeHandle::new(next);
}
let id = next_id();
table.conns.insert(
id,
Conn {
handle: pending,
write: Arc::new(Mutex::new(())),
},
);
Ok(ConnId(id))
}
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(remaining.as_millis())
.unwrap_or(u32::MAX)
.clamp(2, u32::MAX - 1);
let ready = unsafe { WaitNamedPipeW(PCWSTR(name.as_ptr()), timeout_ms) };
if !ready.as_bool() {
return Err(PalError::new(PalErrorKind::Timeout));
}
let handle = unsafe {
CreateFileW(
PCWSTR(name.as_ptr()),
access,
FILE_SHARE_NONE,
None,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
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 = next_id();
table().lock().expect("pipe table").conns.insert(
id,
Conn {
handle: PipeHandle::new(handle),
write: Arc::new(Mutex::new(())),
},
);
return Ok(ConnId(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("pipe write lock");
write_all(handle.as_handle(), &frame)
}
fn recv(&self, conn: ConnId) -> Result<Message, PalError> {
let handle = conn_handle(conn)?;
let mut header = [0_u8; 4];
read_exact(handle.as_handle(), &mut header)?;
let len = u32::from_le_bytes(header);
if !payload_len_ok(len) {
return Err(PalError::new(PalErrorKind::Other));
}
let mut payload = vec![0_u8; len as usize];
read_exact(handle.as_handle(), &mut payload)?;
decode_payload(&payload).map_err(|_error| PalError::new(PalErrorKind::Other))
}
fn disconnect(&self, conn: ConnId) {
let removed = table().lock().expect("pipe table").conns.remove(&conn.0);
if let Some(conn) = removed {
conn.handle.cancel();
}
}
fn close_listener(&self, listener: ListenerId) {
let removed = table()
.lock()
.expect("pipe table")
.listeners
.remove(&listener.0);
if let Some(listener) = removed {
listener.pending.cancel();
}
}
fn pipe_name(&self, nonce: &str) -> String {
format!(r"\\.\pipe\dure-{nonce}")
}
}