use std::hash::{Hash, Hasher};
use std::path::Path;
use tokio::net::windows::named_pipe::{
ClientOptions, NamedPipeClient, NamedPipeServer, ServerOptions,
};
const ERROR_ACCESS_DENIED: i32 = 5;
pub type ControlId = String;
pub type ClientStream = NamedPipeClient;
pub type ServerStream = NamedPipeServer;
pub fn control_id(base: &Path) -> ControlId {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
base.hash(&mut hasher);
format!(r"\\.\pipe\leviath-control-{:016x}", hasher.finish())
}
pub fn control_id_from_str(s: &str) -> ControlId {
s.to_string()
}
pub fn is_daemon_running(id: &str) -> bool {
ClientOptions::new().open(id).is_ok()
}
#[derive(Debug)]
pub struct ControlListener {
name: String,
pending: NamedPipeServer,
}
impl ControlListener {
pub async fn accept(&mut self) -> std::io::Result<Option<ServerStream>> {
self.pending
.connect()
.await
.and_then(|()| ServerOptions::new().create(&self.name))
.map(|next| Some(std::mem::replace(&mut self.pending, next)))
}
}
pub fn bind_control_listener(id: &str) -> std::io::Result<ControlListener> {
let pending = ServerOptions::new()
.first_pipe_instance(true)
.create(id)
.map_err(|e| {
if e.raw_os_error() == Some(ERROR_ACCESS_DENIED) {
std::io::Error::new(
std::io::ErrorKind::AddrInUse,
"a leviath daemon is already running on this control pipe",
)
} else {
e
}
})?;
Ok(ControlListener {
name: id.to_string(),
pending,
})
}
pub async fn connect(id: &str) -> std::io::Result<ClientStream> {
ClientOptions::new().open(id)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn control_id_is_a_pipe_name_stable_per_base() {
let a = control_id(Path::new("/home/x/.leviath"));
assert!(a.starts_with(r"\\.\pipe\leviath-control-"));
assert_eq!(a, control_id(Path::new("/home/x/.leviath")));
assert_ne!(a, control_id(Path::new("/home/y/.leviath")));
}
#[test]
fn control_id_from_str_is_the_name() {
assert_eq!(control_id_from_str(r"\\.\pipe\custom"), r"\\.\pipe\custom");
}
#[tokio::test]
async fn bind_errors_on_invalid_pipe_name() {
assert!(bind_control_listener("not-a-valid-pipe-name").is_err());
}
}