use std::cell::UnsafeCell;
use std::ffi::OsStr;
use std::io::{self, Read};
use std::iter;
use std::os::windows::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use std::ptr;
use std::sync::Arc;
use std::sync::atomic::{Ordering, fence};
use std::thread;
use std::time::{Duration, Instant};
use windows_sys::Win32::Foundation::{
CloseHandle, ERROR_ACCESS_DENIED, ERROR_BROKEN_PIPE, ERROR_IO_PENDING, ERROR_NO_DATA,
ERROR_OPERATION_ABORTED, ERROR_PIPE_BUSY, ERROR_PIPE_CONNECTED, ERROR_PIPE_NOT_CONNECTED,
GetLastError, HANDLE, INVALID_HANDLE_VALUE, STATUS_PENDING, WAIT_OBJECT_0, WAIT_TIMEOUT,
};
use windows_sys::Win32::Storage::FileSystem::{
FILE_FLAG_FIRST_PIPE_INSTANCE, FILE_FLAG_OVERLAPPED, PIPE_ACCESS_DUPLEX, ReadFile, WriteFile,
};
use windows_sys::Win32::System::IO::{CancelIoEx, GetOverlappedResult, OVERLAPPED};
use windows_sys::Win32::System::Pipes::{
ConnectNamedPipe, CreateNamedPipeW, DisconnectNamedPipe, PIPE_READMODE_BYTE,
PIPE_REJECT_REMOTE_CLIENTS, PIPE_TYPE_BYTE, PIPE_WAIT, PeekNamedPipe,
};
use windows_sys::Win32::System::Threading::{
CreateEventW, INFINITE, ResetEvent, SetEvent, WaitForMultipleObjects, WaitForSingleObject,
};
#[path = "windows_security.rs"]
mod security;
use super::Accepted;
use security::{PipeSecurity, Sid};
const ACCEPT_POLL: Duration = Duration::from_millis(50);
const BIND_RETRY: Duration = Duration::from_millis(500);
const BIND_NAP: Duration = Duration::from_millis(20);
const PIPE_BUFFER: u32 = 64 * 1024;
const READ_CHUNK: usize = 8 * 1024;
const MAX_WRITE: usize = 4 * 1024 * 1024;
struct Handle(HANDLE);
unsafe impl Send for Handle {}
unsafe impl Sync for Handle {}
impl Handle {
fn raw(&self) -> HANDLE {
self.0
}
}
impl Drop for Handle {
fn drop(&mut self) {
unsafe { CloseHandle(self.0) };
}
}
type Pipe = Arc<Handle>;
fn event() -> io::Result<Handle> {
let event = unsafe { CreateEventW(ptr::null(), 1, 0, ptr::null()) };
if event.is_null() {
return Err(io::Error::last_os_error());
}
Ok(Handle(event))
}
#[derive(Clone, Copy)]
enum Kind {
Connect,
Read,
Write,
}
enum Woke {
Finished,
Closed,
TimedOut,
}
struct OpState {
overlapped: UnsafeCell<OVERLAPPED>,
event: Handle,
buf: Vec<u8>,
}
struct Op {
pipe: Pipe,
state: Option<Box<OpState>>,
in_flight: bool,
}
unsafe impl Send for Op {}
impl Op {
fn new(pipe: Pipe, capacity: usize) -> io::Result<Self> {
Ok(Self {
pipe,
state: Some(Box::new(OpState {
overlapped: UnsafeCell::new(unsafe { std::mem::zeroed() }),
event: event()?,
buf: vec![0; capacity],
})),
in_flight: false,
})
}
fn idle_state(&mut self) -> io::Result<&mut OpState> {
if self.in_flight {
return Err(io::Error::other(
"an operation on the taria pipe is still in flight",
));
}
self.state.as_deref_mut().ok_or_else(leaked)
}
fn fill(&mut self, bytes: &[u8]) -> io::Result<()> {
let state = self.idle_state()?;
state.buf.clear();
state.buf.extend_from_slice(bytes);
Ok(())
}
fn filled(&self, n: usize) -> &[u8] {
match &self.state {
Some(state) if !self.in_flight => state.buf.get(..n).unwrap_or_default(),
_ => &[],
}
}
fn start(&mut self, kind: Kind, len: usize) -> io::Result<()> {
let pipe = self.pipe.raw();
let state = self.idle_state()?;
let len = u32::try_from(len.min(state.buf.len())).unwrap_or(u32::MAX);
let event = state.event.raw();
if unsafe { ResetEvent(event) } == 0 {
return Err(io::Error::last_os_error());
}
*state.overlapped.get_mut() = OVERLAPPED {
Internal: STATUS_PENDING as u32 as usize,
hEvent: event,
..unsafe { std::mem::zeroed() }
};
let overlapped = state.overlapped.get();
let buf = state.buf.as_mut_ptr();
let ok = unsafe {
match kind {
Kind::Connect => ConnectNamedPipe(pipe, overlapped),
Kind::Read => ReadFile(pipe, buf, len, ptr::null_mut(), overlapped),
Kind::Write => WriteFile(pipe, buf.cast_const(), len, ptr::null_mut(), overlapped),
}
};
if ok == 0 {
let err = unsafe { GetLastError() };
if err != ERROR_IO_PENDING {
return Err(os_error(err));
}
}
self.in_flight = true;
Ok(())
}
fn wait(&self, closed: Option<&Handle>, timeout_ms: u32) -> io::Result<Woke> {
let state = self.state.as_deref().ok_or_else(leaked)?;
if self.in_flight && !still_pending(state) {
return Ok(Woke::Finished);
}
let handles = [
state.event.raw(),
closed.map_or(ptr::null_mut(), Handle::raw),
];
let count = if closed.is_some() { 2 } else { 1 };
let woke = unsafe { WaitForMultipleObjects(count, handles.as_ptr(), 0, timeout_ms) };
match woke {
WAIT_OBJECT_0 => Ok(Woke::Finished),
WAIT_TIMEOUT => Ok(Woke::TimedOut),
woke if woke == WAIT_OBJECT_0 + 1 => Ok(Woke::Closed),
_ => Err(io::Error::last_os_error()),
}
}
fn finish(&mut self) -> io::Result<u32> {
if !self.in_flight {
return Err(io::Error::other(
"no operation on the taria pipe is in flight",
));
}
let state = self.state.as_deref().ok_or_else(leaked)?;
let mut moved = 0u32;
let ok =
unsafe { GetOverlappedResult(self.pipe.raw(), state.overlapped.get(), &mut moved, 1) };
let err = if ok == 0 {
unsafe { GetLastError() }
} else {
0
};
if still_pending(state) {
return Err(if ok == 0 {
os_error(err)
} else {
io::Error::other("the taria pipe reported an unfinished operation as done")
});
}
self.in_flight = false;
if ok == 0 {
Err(os_error(err))
} else {
Ok(moved)
}
}
fn cancel(&mut self) -> io::Result<u32> {
if !self.in_flight {
return Err(io::Error::other(
"no operation on the taria pipe is in flight",
));
}
if let Some(state) = self.state.as_deref() {
unsafe { CancelIoEx(self.pipe.raw(), state.overlapped.get()) };
}
self.finish()
}
}
impl Drop for Op {
fn drop(&mut self) {
if self.in_flight {
let _ = self.cancel();
}
if self.in_flight
&& let Some(state) = self.state.take()
{
std::mem::forget(state);
}
}
}
fn still_pending(state: &OpState) -> bool {
let internal = unsafe { ptr::read_volatile(&raw const (*state.overlapped.get()).Internal) };
internal == STATUS_PENDING as u32 as usize
}
fn leaked() -> io::Error {
io::Error::other("an operation on the taria pipe never finished, so the pipe is unusable")
}
struct Session {
closed: Handle,
}
impl Session {
fn new() -> io::Result<Self> {
Ok(Self { closed: event()? })
}
fn close(&self) -> io::Result<()> {
if unsafe { SetEvent(self.closed.raw()) } == 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
fn is_closed(&self) -> bool {
unsafe { WaitForSingleObject(self.closed.raw(), 0) == WAIT_OBJECT_0 }
}
}
pub(crate) struct Listener {
path: PathBuf,
pipe: Pipe,
connect: Op,
last: Option<Arc<Session>>,
}
enum Connect {
Connected,
Shutdown,
Again,
Failed,
}
impl Listener {
pub(crate) fn bind(path: PathBuf) -> io::Result<Self> {
let Some(name) = path.to_str() else {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!(
"{} is not valid Unicode, so it cannot name a pipe",
path.display()
),
));
};
taria::socket::check_pipe_name(name)
.map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err.to_string()))?;
let wide: Vec<u16> = OsStr::new(name)
.encode_wide()
.chain(iter::once(0))
.collect();
let user = Sid::current_user()?;
let security = PipeSecurity::owned_by(&user)?;
let attributes = security.attributes();
let until = Instant::now() + BIND_RETRY;
let handle = loop {
let handle = unsafe {
CreateNamedPipeW(
wide.as_ptr(),
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS,
1,
PIPE_BUFFER,
PIPE_BUFFER,
0,
&attributes,
)
};
if handle != INVALID_HANDLE_VALUE {
break handle;
}
let code = unsafe { GetLastError() };
if !name_in_use(code) || Instant::now() >= until {
return Err(create_error(name, code));
}
thread::sleep(BIND_NAP);
};
let pipe = Arc::new(Handle(handle));
let connect = Op::new(Arc::clone(&pipe), 0)?;
Ok(Self {
path,
pipe,
connect,
last: None,
})
}
pub(crate) fn path(&self) -> &Path {
&self.path
}
pub(crate) fn accept(&mut self, shutdown: impl Fn() -> bool) -> Option<Accepted> {
loop {
if shutdown() {
return None;
}
if !self.retire_last(&shutdown) {
return None;
}
let Ok(session) = Session::new() else {
thread::sleep(ACCEPT_POLL);
continue;
};
match self.listen(&shutdown) {
Connect::Connected => {
if shutdown() {
return None;
}
let session = Arc::new(session);
self.last = Some(Arc::clone(&session));
return Some(Accepted {
connection: Connection {
pipe: Arc::clone(&self.pipe),
session,
},
waited: false,
});
}
Connect::Shutdown => return None,
Connect::Again => {}
Connect::Failed => thread::sleep(ACCEPT_POLL),
}
}
}
fn retire_last(&mut self, shutdown: &impl Fn() -> bool) -> bool {
let Some(last) = self.last.take() else {
return true;
};
let _ = last.close();
while Arc::strong_count(&last) > 1 {
if shutdown() {
return false;
}
thread::sleep(ACCEPT_POLL);
}
fence(Ordering::Acquire);
drop(last);
self.disconnect();
true
}
fn disconnect(&self) -> bool {
unsafe { DisconnectNamedPipe(self.pipe.raw()) != 0 }
}
fn listen(&mut self, shutdown: &impl Fn() -> bool) -> Connect {
if let Err(err) = self.connect.start(Kind::Connect, 0) {
return self.listened(Err(err));
}
let slice = millis_ceil(ACCEPT_POLL);
loop {
match self.connect.wait(None, slice) {
Ok(Woke::Finished) => {
let result = self.connect.finish();
return self.listened(result);
}
Ok(Woke::TimedOut | Woke::Closed) => {
if shutdown() {
let _ = self.connect.cancel();
return Connect::Shutdown;
}
}
Err(_) => {
let result = self.connect.cancel();
return self.listened(result);
}
}
}
}
fn listened(&mut self, result: io::Result<u32>) -> Connect {
match result.map_err(|err| err.raw_os_error()) {
Ok(_) => Connect::Connected,
Err(Some(code)) if code == ERROR_PIPE_CONNECTED as i32 => Connect::Connected,
Err(Some(code)) if code == ERROR_NO_DATA as i32 => {
if self.disconnect() {
Connect::Again
} else {
Connect::Failed
}
}
Err(_) => {
self.disconnect();
Connect::Failed
}
}
}
}
fn name_in_use(code: u32) -> bool {
code == ERROR_ACCESS_DENIED || code == ERROR_PIPE_BUSY
}
fn create_error(name: &str, code: u32) -> io::Error {
if name_in_use(code) {
return io::Error::new(
io::ErrorKind::AddrInUse,
format!(
"another instance is already serving the taria pipe {name}, or something else \
holds that name (a bridge still connected to an instance that has exited keeps \
it until it lets go); refusing to take it over, because that would leave it \
running with no way for a bridge to reach it. To run a second instance \
alongside it, give it a pipe of its own: set TARIA_SOCK to a different name of \
the form {}<name>.",
taria::socket::LOCAL_PIPE_PREFIX
),
);
}
let err = os_error(code);
io::Error::new(
err.kind(),
format!("cannot create the taria pipe {name}: {err}"),
)
}
pub(crate) struct Connection {
pipe: Pipe,
session: Arc<Session>,
}
impl Connection {
pub(crate) fn set_write_timeout(&self, timeout: Duration) -> io::Result<()> {
if timeout.is_zero() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"cannot set a 0 duration timeout",
));
}
Ok(())
}
pub(crate) fn split(self) -> io::Result<(Reader, Writer)> {
let reader = Reader {
op: Op::new(Arc::clone(&self.pipe), READ_CHUNK)?,
session: Arc::clone(&self.session),
};
let writer = Writer {
op: Op::new(self.pipe, 0)?,
session: self.session,
};
Ok((reader, writer))
}
}
pub(crate) struct Reader {
op: Op,
session: Arc<Session>,
}
impl Read for Reader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.read_up_to(buf, buf.len())
}
}
impl Reader {
pub(crate) fn with_nonblocking_reads<R>(
&mut self,
f: impl FnOnce(&mut dyn Read) -> R,
) -> io::Result<R> {
Ok(f(&mut ReadyOnly(self)))
}
fn read_up_to(&mut self, buf: &mut [u8], len: usize) -> io::Result<usize> {
let len = len.min(buf.len()).min(READ_CHUNK);
if len == 0 || self.session.is_closed() {
return Ok(0);
}
match self.op.start(Kind::Read, len) {
Ok(()) => {}
Err(err) if is_disconnect(&err) => return Ok(0),
Err(err) => return Err(err),
}
let read = loop {
match self.op.wait(Some(&self.session.closed), INFINITE) {
Ok(Woke::Finished) => break self.op.finish(),
Ok(Woke::Closed) => {
break match self.op.cancel() {
Err(err) if err.raw_os_error() == Some(ERROR_OPERATION_ABORTED as i32) => {
Ok(0)
}
result => result,
};
}
Ok(Woke::TimedOut) => {}
Err(err) => {
let _ = self.op.cancel();
return Err(err);
}
}
};
match read {
Ok(n) => {
let data = self.op.filled(n as usize);
let Some(into) = buf.get_mut(..data.len()) else {
return Err(io::Error::other(
"the taria pipe read more than it was asked for",
));
};
into.copy_from_slice(data);
Ok(data.len())
}
Err(err) if is_disconnect(&err) => Ok(0),
Err(err) => Err(err),
}
}
fn waiting(&self) -> io::Result<Option<u32>> {
let mut waiting = 0u32;
let ok = unsafe {
PeekNamedPipe(
self.op.pipe.raw(),
ptr::null_mut(),
0,
ptr::null_mut(),
&mut waiting,
ptr::null_mut(),
)
};
if ok == 0 {
let err = io::Error::last_os_error();
return if is_disconnect(&err) {
Ok(None)
} else {
Err(err)
};
}
Ok(Some(waiting))
}
}
struct ReadyOnly<'a>(&'a mut Reader);
impl Read for ReadyOnly<'_> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if buf.is_empty() || self.0.session.is_closed() {
return Ok(0);
}
match self.0.waiting()? {
None => Ok(0),
Some(0) => Err(io::ErrorKind::WouldBlock.into()),
Some(waiting) => self.0.read_up_to(buf, waiting as usize),
}
}
}
pub(crate) struct Writer {
op: Op,
session: Arc<Session>,
}
impl Writer {
pub(crate) fn write_all_before(
&mut self,
mut bytes: &[u8],
deadline: Instant,
) -> io::Result<()> {
while !bytes.is_empty() {
if self.session.is_closed() {
return Err(shut_down());
}
if deadline <= Instant::now() {
return Err(io::ErrorKind::TimedOut.into());
}
let chunk = bytes.get(..MAX_WRITE).unwrap_or(bytes);
match self.write_before(chunk, deadline)? {
0 => return Err(io::ErrorKind::WriteZero.into()),
n => bytes = bytes.get(n..).unwrap_or_default(),
}
}
Ok(())
}
fn write_before(&mut self, chunk: &[u8], deadline: Instant) -> io::Result<usize> {
self.op.fill(chunk)?;
self.op.start(Kind::Write, chunk.len())?;
let whole = |moved: u32| moved as usize == chunk.len();
loop {
let left = deadline.saturating_duration_since(Instant::now());
let woke = if left.is_zero() {
Ok(Woke::TimedOut)
} else {
self.op.wait(Some(&self.session.closed), millis_ceil(left))
};
match woke {
Ok(Woke::Finished) => return self.op.finish().map(|moved| moved as usize),
Ok(Woke::TimedOut) if Instant::now() < deadline => {}
Ok(Woke::TimedOut) => {
return match self.op.cancel() {
Ok(moved) if whole(moved) => Ok(moved as usize),
_ => Err(io::ErrorKind::TimedOut.into()),
};
}
Ok(Woke::Closed) => {
return match self.op.cancel() {
Ok(moved) if whole(moved) => Ok(moved as usize),
_ => Err(shut_down()),
};
}
Err(err) => {
let _ = self.op.cancel();
return Err(err);
}
}
}
}
pub(crate) fn shutdown(&self) -> io::Result<()> {
self.session.close()
}
}
fn shut_down() -> io::Error {
io::Error::new(
io::ErrorKind::NotConnected,
"the taria pipe's session was shut down",
)
}
fn is_disconnect(err: &io::Error) -> bool {
matches!(
err.raw_os_error().map(|code| code as u32),
Some(ERROR_BROKEN_PIPE | ERROR_PIPE_NOT_CONNECTED | ERROR_NO_DATA)
)
}
fn millis_ceil(duration: Duration) -> u32 {
let millis = duration.as_nanos().div_ceil(1_000_000);
u32::try_from(millis)
.unwrap_or(INFINITE - 1)
.min(INFINITE - 1)
}
fn os_error(code: u32) -> io::Error {
io::Error::from_raw_os_error(code as i32)
}
pub(crate) fn user_identity() -> io::Result<String> {
Sid::current_user()
.and_then(|sid| sid.to_string_sid())
.map_err(|err| {
io::Error::new(
err.kind(),
format!(
"cannot read this user's SID from the process token, which names the default \
taria pipe: {err}; set TARIA_SOCK to a pipe name of the form {}<name> to \
bind without it",
taria::socket::LOCAL_PIPE_PREFIX
),
)
})
}
#[cfg(all(test, windows))]
mod tests {
use std::env;
use std::fs::File;
use std::io::{BufRead, BufReader, Write};
use std::os::windows::io::FromRawHandle;
use std::sync::atomic::{AtomicBool, AtomicUsize};
use std::sync::mpsc;
use taria::wire::{AppToBridge, BridgeToApp, InputId, InputStatus};
use taria::{AgentInput, PROTOCOL_VERSION};
use windows_sys::Win32::Foundation::{GENERIC_READ, GENERIC_WRITE};
use windows_sys::Win32::Security::Authorization::{
ConvertStringSidToSidW, GetSecurityInfo, SE_KERNEL_OBJECT,
};
use windows_sys::Win32::Security::{
ACCESS_ALLOWED_ACE, ACE_HEADER, ACL, DACL_SECURITY_INFORMATION, EqualSid, GetAce,
OWNER_SECURITY_INFORMATION, PSID,
};
use windows_sys::Win32::Storage::FileSystem::{
CreateFileW, FILE_GENERIC_READ, FILE_GENERIC_WRITE, OPEN_EXISTING,
};
use super::security::LocalMemory;
use super::*;
use crate::TariaLayer;
const TIMEOUT: Duration = Duration::from_secs(5);
const ACCESS_ALLOWED_ACE_TYPE: u8 = 0;
fn unique_name(label: &str) -> String {
static NEXT: AtomicUsize = AtomicUsize::new(0);
format!(
r"\\.\pipe\taria-transport-test-{}-{}-{label}",
std::process::id(),
NEXT.fetch_add(1, Ordering::Relaxed)
)
}
fn wide(text: &str) -> Vec<u16> {
text.encode_utf16().chain(iter::once(0)).collect()
}
fn bind(label: &str) -> Listener {
Listener::bind(PathBuf::from(unique_name(label))).expect("bind a test pipe")
}
fn name_of(listener: &Listener) -> String {
listener
.path()
.to_str()
.expect("a test name is Unicode")
.to_owned()
}
fn open_client(name: &str) -> io::Result<File> {
let wide = wide(name);
let handle = unsafe {
CreateFileW(
wide.as_ptr(),
GENERIC_READ | GENERIC_WRITE,
0,
ptr::null(),
OPEN_EXISTING,
0,
ptr::null_mut(),
)
};
if handle == INVALID_HANDLE_VALUE {
return Err(io::Error::last_os_error());
}
Ok(unsafe { File::from_raw_handle(handle) })
}
fn connect_client(name: &str) -> File {
let until = Instant::now() + TIMEOUT;
loop {
match open_client(name) {
Ok(file) => return file,
Err(err)
if err.raw_os_error() == Some(ERROR_PIPE_BUSY as i32)
&& Instant::now() < until =>
{
thread::sleep(Duration::from_millis(5));
}
Err(err) => panic!("cannot open {name}: {err}"),
}
}
}
fn accept_async(
mut listener: Listener,
stop: Arc<AtomicBool>,
) -> mpsc::Receiver<(Listener, Option<Accepted>)> {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let accepted = listener.accept(|| stop.load(Ordering::SeqCst));
let _ = tx.send((listener, accepted));
});
rx
}
fn next_session(listener: Listener) -> (Listener, Connection, File) {
let name = name_of(&listener);
let accepting = accept_async(listener, Arc::new(AtomicBool::new(false)));
let client = connect_client(&name);
let (listener, accepted) = accepting
.recv_timeout(TIMEOUT)
.expect("accept returns once a client connects");
let accepted = accepted.expect("the client is accepted");
assert!(!accepted.waited, "a pipe has no backlog to wait in");
(listener, accepted.connection, client)
}
fn session(label: &str) -> (Listener, Connection, File) {
next_session(bind(label))
}
fn read_line(from: &mut impl BufRead) -> String {
let mut line = String::new();
from.read_line(&mut line).expect("read a line");
line
}
#[test]
fn a_line_goes_each_way() {
let (_listener, connection, client) = session("roundtrip");
connection
.set_write_timeout(TIMEOUT)
.expect("a nonzero timeout");
let (mut reader, mut writer) = connection.split().expect("split");
writer
.write_all_before(b"hello\n", Instant::now() + TIMEOUT)
.expect("write a line");
let mut from_app = BufReader::new(client.try_clone().expect("clone the client"));
assert_eq!(read_line(&mut from_app), "hello\n");
(&client).write_all(b"world\n").expect("the bridge writes");
assert_eq!(read_line(&mut BufReader::new(&mut reader)), "world\n");
}
#[test]
fn a_second_bind_of_the_same_name_is_refused_as_in_use() {
let first = bind("twice");
let name = name_of(&first);
let tried = Instant::now();
let Err(err) = Listener::bind(PathBuf::from(&name)) else {
panic!("a second instance must not get the first's pipe");
};
let took = tried.elapsed();
assert!(
took >= BIND_RETRY,
"refused after {took:?}, inside the retry"
);
assert!(
took < BIND_RETRY + Duration::from_millis(500),
"refused only after {took:?}"
);
assert_eq!(err.kind(), io::ErrorKind::AddrInUse, "err: {err}");
let message = err.to_string();
assert!(message.contains("already serving"), "err: {message}");
assert!(message.contains("TARIA_SOCK"), "err: {message}");
let (_first, _connection, _client) = next_session(first);
}
#[test]
fn a_bind_waits_for_a_name_in_use_to_come_free() {
let first = bind("comesfree");
let name = name_of(&first);
let holder = thread::spawn(move || {
thread::sleep(Duration::from_millis(150));
drop(first);
});
let tried = Instant::now();
let second = Listener::bind(PathBuf::from(&name)).expect("the name came free in time");
let took = tried.elapsed();
holder.join().expect("the holder thread");
assert!(took < BIND_RETRY, "bound only after {took:?}");
let (_second, _connection, _client) = next_session(second);
}
#[test]
fn a_bind_succeeds_in_time_while_a_bridge_holds_the_last_instance() {
let (listener, connection, client) = session("lingering");
let name = name_of(&listener);
drop((connection, listener));
let bridge = thread::spawn(move || {
thread::sleep(Duration::from_millis(150));
drop(client);
});
let tried = Instant::now();
let next = Listener::bind(PathBuf::from(&name)).expect("bound within the retry");
let took = tried.elapsed();
bridge.join().expect("the bridge thread");
assert!(took < BIND_RETRY, "bound only after {took:?}");
if took < Duration::from_millis(100) {
eprintln!(
"bound in {took:?}, before the bridge closed its end: this system freed the name \
with the app's handles, so the wait was not exercised"
);
}
let (_next, _connection, _client) = next_session(next);
}
#[test]
fn the_pipe_is_owned_by_its_user_and_lets_nobody_else_in() {
let listener = bind("dacl");
let me = Sid::current_user().expect("read the user");
let everyone = string_sid("S-1-1-0");
let anonymous = string_sid("S-1-5-7");
let mut owner: PSID = ptr::null_mut();
let mut dacl: *mut ACL = ptr::null_mut();
let mut descriptor = ptr::null_mut();
let code = unsafe {
GetSecurityInfo(
listener.pipe.raw(),
SE_KERNEL_OBJECT,
OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
&mut owner,
ptr::null_mut(),
&mut dacl,
ptr::null_mut(),
&mut descriptor,
)
};
assert_eq!(code, 0, "{}", os_error(code));
let _descriptor = LocalMemory(descriptor);
assert_ne!(
unsafe { EqualSid(owner, me.as_psid()) },
0,
"the user owns the pipe, even from an elevated token"
);
assert!(!dacl.is_null(), "a null DACL would let everyone in");
let count = unsafe { (*dacl).AceCount };
assert!(
count >= 1,
"a DACL with no entries lets nobody in, the user included"
);
for index in 0..count {
let mut ace = ptr::null_mut();
assert_ne!(unsafe { GetAce(dacl, index.into(), &mut ace) }, 0);
let header = unsafe { *ace.cast::<ACE_HEADER>() };
assert_eq!(header.AceType, ACCESS_ALLOWED_ACE_TYPE, "ACE {index}");
let allowed = ace.cast::<ACCESS_ALLOWED_ACE>();
let (sid, mask): (PSID, u32) =
unsafe { ((&raw mut (*allowed).SidStart).cast(), (*allowed).Mask) };
unsafe {
assert_eq!(EqualSid(sid, everyone.0), 0, "no Everyone entry");
assert_eq!(EqualSid(sid, anonymous.0), 0, "no anonymous entry");
assert_ne!(EqualSid(sid, me.as_psid()), 0, "ACE {index} is the user's");
}
let allowed_rights =
GENERIC_READ | GENERIC_WRITE | FILE_GENERIC_READ | FILE_GENERIC_WRITE;
assert_eq!(mask & !allowed_rights, 0, "ACE {index} grants {mask:#x}");
}
}
fn string_sid(text: &str) -> LocalMemory {
let wide = wide(text);
let mut sid = ptr::null_mut();
assert_ne!(
unsafe { ConvertStringSidToSidW(wide.as_ptr(), &mut sid) },
0
);
LocalMemory(sid)
}
#[test]
fn a_second_client_is_told_the_pipe_is_busy() {
let (listener, _connection, _client) = session("busy");
let err = open_client(&name_of(&listener)).expect_err("one bridge at a time");
assert_eq!(
err.raw_os_error(),
Some(ERROR_PIPE_BUSY as i32),
"err: {err}"
);
}
#[test]
fn the_write_deadline_fires_against_a_bridge_that_never_reads() {
let (_listener, connection, _client) = session("deadline");
let (_reader, mut writer) = connection.split().expect("split");
let line = vec![b'x'; 4 * 1024 * 1024];
let budget = Duration::from_millis(300);
let started = Instant::now();
let err = writer
.write_all_before(&line, started + budget)
.expect_err("a bridge that reads nothing cannot take 4 MiB");
let took = started.elapsed();
assert_eq!(err.kind(), io::ErrorKind::TimedOut, "err: {err}");
assert!(took >= budget, "gave up early, after {took:?}");
assert!(
took < budget + Duration::from_millis(200),
"gave up {:?} past the deadline",
took - budget
);
assert!(
!writer.op.in_flight && writer.op.state.is_some(),
"the cancelled write was waited for, and its buffer kept, not leaked"
);
}
#[test]
fn shutdown_wakes_a_blocked_reader_and_every_later_read_returns_at_once() {
let (_listener, connection, _client) = session("wake");
let (mut reader, writer) = connection.split().expect("split");
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let mut buf = [0u8; 64];
let first = reader.read(&mut buf).map_err(|err| err.kind());
let woke = Instant::now();
let again = reader.read(&mut buf).map_err(|err| err.kind());
let again_took = woke.elapsed();
let _ = tx.send((first, woke, again, again_took));
});
thread::sleep(Duration::from_millis(200));
let shut = Instant::now();
writer.shutdown().expect("shut down");
let (first, woke, again, again_took) = rx
.recv_timeout(TIMEOUT)
.expect("the shutdown wakes the reader");
let latency = woke.duration_since(shut);
assert_eq!(first, Ok(0), "end of stream");
assert!(
latency < Duration::from_millis(100),
"woke after {latency:?}"
);
assert_eq!(again, Ok(0), "a read after the shutdown is end of stream");
assert!(
again_took < Duration::from_millis(100),
"a read after the shutdown took {again_took:?}"
);
}
#[test]
fn a_write_after_shutdown_fails_at_once() {
let (_listener, connection, _client) = session("shutwrite");
let (_reader, mut writer) = connection.split().expect("split");
writer.shutdown().expect("shut down");
let started = Instant::now();
let err = writer
.write_all_before(b"late\n", started + TIMEOUT)
.expect_err("the session is over");
assert_eq!(err.kind(), io::ErrorKind::NotConnected, "err: {err}");
assert!(started.elapsed() < Duration::from_millis(100));
}
#[test]
fn shutdown_wakes_a_blocked_accept() {
let listener = bind("wakeaccept");
let stop = Arc::new(AtomicBool::new(false));
let accepting = accept_async(listener, Arc::clone(&stop));
thread::sleep(Duration::from_millis(200));
let stopped = Instant::now();
stop.store(true, Ordering::SeqCst);
let (_listener, accepted) = accepting
.recv_timeout(TIMEOUT)
.expect("the flag wakes the listener");
let latency = stopped.elapsed();
assert!(accepted.is_none(), "nothing was accepted");
assert!(
latency < Duration::from_millis(100),
"woke after {latency:?}"
);
}
#[test]
fn the_instance_is_reused_session_after_session() {
let mut listener = bind("reuse");
let handle = listener.pipe.raw();
for round in 0..3 {
let (next, connection, client) = next_session(listener);
listener = next;
assert_eq!(listener.pipe.raw(), handle, "the one instance, every time");
let (mut reader, mut writer) = connection.split().expect("split");
let deadline = Instant::now() + TIMEOUT;
let mut from_app = BufReader::new(client.try_clone().expect("clone the client"));
writer
.write_all_before(format!("round {round}\n").as_bytes(), deadline)
.expect("write");
assert_eq!(
read_line(&mut from_app),
format!("round {round}\n"),
"this session's own line, and nothing the last one left unread"
);
writer
.write_all_before(b"left behind\n", deadline)
.expect("write");
drop(from_app);
drop(client);
let mut buf = [0u8; 16];
assert_eq!(
reader.read(&mut buf).expect("read"),
0,
"end of stream once the bridge has gone"
);
writer.shutdown().expect("shut down");
}
}
#[test]
fn what_was_written_before_shutdown_survives_the_pipe_closing() {
let (listener, connection, client) = session("flush");
let (reader, mut writer) = connection.split().expect("split");
writer
.write_all_before(
b"ack 1\nack 2\n",
Instant::now() + Duration::from_millis(250),
)
.expect("flush");
writer.shutdown().expect("shut down");
drop((reader, writer, listener));
let mut from_app = BufReader::new(client);
assert_eq!(read_line(&mut from_app), "ack 1\n");
assert_eq!(read_line(&mut from_app), "ack 2\n");
assert_eq!(read_line(&mut from_app), "", "then end of stream");
}
#[test]
fn nonblocking_reads_take_what_is_there_and_then_would_block() {
let (_listener, connection, client) = session("ready");
let (mut reader, _writer) = connection.split().expect("split");
(&client).write_all(b"early\n").expect("the bridge writes");
let (got, last) = reader
.with_nonblocking_reads(|ready| {
let mut got = Vec::new();
let mut buf = [0u8; 4];
loop {
match ready.read(&mut buf) {
Ok(0) => return (got, None),
Ok(n) => got.extend_from_slice(&buf[..n]),
Err(err) => return (got, Some(err.kind())),
}
}
})
.expect("the reads run");
assert_eq!(got, b"early\n");
assert_eq!(last, Some(io::ErrorKind::WouldBlock));
}
#[test]
fn names_that_are_not_a_local_pipe_are_refused() {
for name in [
r"\\host\pipe\taria",
r"\\.\pipe\..\UNC\host\share",
r"\\.\pipe\a/b",
r"\\?\pipe\taria",
r"C:\Users\someone\app.sock",
"app.sock",
] {
let Err(err) = Listener::bind(PathBuf::from(name)) else {
panic!("{name} was bound");
};
assert_eq!(err.kind(), io::ErrorKind::InvalidInput, "{name}: {err}");
}
let Err(err) = Listener::bind(PathBuf::from(r"\\host\pipe\taria")) else {
panic!("a remote pipe was bound");
};
assert!(err.to_string().contains("SMB"), "err: {err}");
assert!(!err.to_string().contains("TARIA_SOCK"), "err: {err}");
}
#[test]
fn the_user_identity_is_a_sid_that_names_a_pipe() {
let sid = user_identity().expect("read the SID");
assert!(sid.starts_with("S-1-"), "{sid}");
let name = taria::socket::pipe_name("demo", &sid).expect("a SID names a pipe");
assert_eq!(name, format!(r"\\.\pipe\taria-{sid}-demo"));
}
#[test]
fn the_default_endpoint_is_the_users_own_pipe() {
if env::var_os("TARIA_SOCK").is_some_and(|name| !name.is_empty()) {
return;
}
let label = format!("default-{}", std::process::id());
let sid = user_identity().expect("read the SID");
let layer = TariaLayer::bind_or_disabled(&label);
assert!(layer.is_enabled(), "{:?}", layer.bind_error());
assert_eq!(
layer.socket_path(),
Path::new(&format!(r"\\.\pipe\taria-{sid}-{label}"))
);
}
#[test]
fn a_layer_serves_a_bridge_and_its_last_ack_survives_exit() {
let name = unique_name("layer");
let layer = TariaLayer::bind_at("pipe-e2e", &name).expect("bind the layer");
assert_eq!(layer.socket_path(), Path::new(&name));
let client = connect_client(&name);
let mut from_app = BufReader::new(client.try_clone().expect("clone the client"));
let hello: AppToBridge =
serde_json::from_str(&read_line(&mut from_app)).expect("a message");
assert_eq!(
hello,
AppToBridge::hello("pipe-e2e", PROTOCOL_VERSION).stamps(true)
);
let mut line = serde_json::to_string(&BridgeToApp::input(
InputId::new(7),
AgentInput::key("enter"),
))
.expect("serialize");
line.push('\n');
(&client).write_all(line.as_bytes()).expect("send an input");
let (id, _input) = layer.recv_timeout(TIMEOUT).expect("the input arrives");
assert_eq!(id, InputId::new(7));
let dropped = Instant::now();
drop(layer);
assert!(
dropped.elapsed() < Duration::from_secs(1),
"drop took {:?}",
dropped.elapsed()
);
let mut messages = Vec::new();
loop {
let line = read_line(&mut from_app);
if line.is_empty() {
break;
}
messages.push(serde_json::from_str::<AppToBridge>(&line).expect("a message"));
}
assert!(
messages.contains(&AppToBridge::ack(InputId::new(7), InputStatus::Delivered)),
"the ack reached the bridge before the pipe closed: {messages:?}"
);
}
}