#[cfg(feature = "ipc")]
pub use crate::{
ipc_current_user_id as current_user_id, IpcEndpoint as Endpoint,
IpcInheritedListener as InheritedListener, IpcListener as Listener,
IpcListenerNonblockingMode as ListenerNonblockingMode, IpcPeerIdentity as PeerIdentity,
IpcPeerIdentitySource as PeerIdentitySource, IpcStream as Stream,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct HandoffAttachment {
protocol_value: u64,
backend_may_adopt_before_offer: bool,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg(feature = "ipc")]
pub struct EndpointAddressCandidates {
kernel_namespace: Option<String>,
filesystem: Option<std::path::PathBuf>,
}
#[cfg(feature = "ipc")]
impl EndpointAddressCandidates {
pub fn new(kernel_namespace: Option<String>, filesystem: Option<std::path::PathBuf>) -> Self {
Self {
kernel_namespace,
filesystem,
}
}
pub fn select(self) -> Option<String> {
crate::ipc_select_endpoint_address(self.kernel_namespace, self.filesystem)
}
}
impl HandoffAttachment {
#[cfg(feature = "ipc")]
pub(crate) fn new(protocol_value: u64, backend_may_adopt_before_offer: bool) -> Self {
Self {
protocol_value,
backend_may_adopt_before_offer,
}
}
pub fn append_unsigned_varint(self, output: &mut Vec<u8>) {
let mut value = self.protocol_value;
while value >= 0x80 {
output.push((value as u8 & 0x7f) | 0x80);
value >>= 7;
}
output.push(value as u8);
}
pub fn backend_may_adopt_before_offer(self) -> bool {
self.backend_may_adopt_before_offer
}
}
#[cfg(feature = "ipc")]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum OwnerPrivateDirectoryOutcome {
AlreadyPrivate,
Hardened,
}
#[cfg(feature = "ipc")]
pub fn ensure_owner_private_directory(
path: &std::path::Path,
) -> std::io::Result<OwnerPrivateDirectoryOutcome> {
crate::ipc_ensure_owner_private_directory(path)
}
#[cfg(feature = "ipc")]
pub fn owner_private_directory(path: &std::path::Path) -> std::io::Result<bool> {
crate::ipc_owner_private_directory(path)
}
#[cfg(feature = "ipc")]
pub fn nonblocking_zero_read_is_pending() -> bool {
crate::ipc_nonblocking_zero_read_is_pending()
}
#[cfg(feature = "ipc")]
pub fn endpoint_is_filesystem_backed() -> bool {
crate::ipc_endpoint_is_filesystem_backed()
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HandoffTransferErrorKind {
Unsupported,
PermissionDenied,
BackendUnavailable,
WouldBlock,
Failed,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HandoffTransferError {
kind: HandoffTransferErrorKind,
may_have_reached_backend: bool,
detail: String,
}
impl HandoffTransferError {
#[cfg(feature = "ipc")]
pub(crate) fn new(
kind: HandoffTransferErrorKind,
may_have_reached_backend: bool,
detail: impl Into<String>,
) -> Self {
Self {
kind,
may_have_reached_backend,
detail: detail.into(),
}
}
pub fn kind(&self) -> HandoffTransferErrorKind {
self.kind
}
pub fn may_have_reached_backend(&self) -> bool {
self.may_have_reached_backend
}
}
impl std::fmt::Display for HandoffTransferError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str(&self.detail)
}
}
impl std::error::Error for HandoffTransferError {}
#[cfg(feature = "ipc")]
pub fn broker_endpoint_name(bare_name: &str, path_scoped: bool) -> std::io::Result<String> {
crate::IpcBrokerEndpointName(bare_name, path_scoped)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct EndpointNameLimit {
pub max_bytes: usize,
pub label: &'static str,
}
#[cfg(feature = "ipc")]
pub fn endpoint_name_limit() -> EndpointNameLimit {
crate::ipc_endpoint_name_limit()
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct EndpointNameTooLong {
pub len: usize,
pub max: usize,
pub limit_label: &'static str,
}
#[cfg(feature = "ipc")]
pub(crate) fn per_user_runtime_fallback() -> std::path::PathBuf {
dirs::cache_dir()
.or_else(dirs::data_local_dir)
.or_else(dirs::home_dir)
.unwrap_or_else(std::env::temp_dir)
.join("running-process")
.join("broker-v2")
}
#[cfg(feature = "ipc")]
pub fn endpoint_scope_bytes(path: &std::path::Path) -> Vec<u8> {
crate::ipc_endpoint_scope_bytes(path)
}
#[cfg(feature = "ipc")]
pub fn broker_v2_runtime_dir() -> std::path::PathBuf {
crate::ipc_broker_v2_runtime_dir()
}
#[cfg(feature = "ipc")]
pub fn broker_v1_endpoint_path(bare_name: &str) -> Result<String, EndpointNameTooLong> {
crate::ipc_broker_v1_endpoint_path(bare_name)
}
#[cfg(feature = "ipc-async")]
pub use crate::{
IpcAsyncListener as AsyncListener, IpcAsyncReadHalf as AsyncReadHalf,
IpcAsyncStream as AsyncStream, IpcAsyncWriteHalf as AsyncWriteHalf,
IpcIntoAsyncListener as IntoAsyncListener, IpcIntoAsyncStream as IntoAsyncStream,
};
#[cfg(all(test, feature = "ipc"))]
mod tests {
use std::io::{Read, Write};
use super::{
current_user_id, ensure_owner_private_directory, owner_private_directory, Endpoint,
HandoffAttachment, Listener, Stream,
};
#[test]
fn ensure_private_dir_passes_private_check() {
let temporary = tempfile::tempdir().expect("temporary directory");
let path = temporary.path().join("private");
ensure_owner_private_directory(&path).expect("harden directory");
assert!(owner_private_directory(&path).expect("inspect directory"));
}
#[test]
fn handoff_attachment_can_be_encoded_without_exposing_its_value() {
let mut encoded = Vec::new();
HandoffAttachment::new(300, false).append_unsigned_varint(&mut encoded);
assert_eq!(encoded, [0xac, 0x02]);
}
#[test]
fn handoff_attachment_reports_pre_offer_adoption_semantics() {
assert!(HandoffAttachment::new(0, true).backend_may_adopt_before_offer());
assert!(!HandoffAttachment::new(0, false).backend_may_adopt_before_offer());
}
#[test]
fn endpoint_lifecycle_mechanics_are_facade_owned() {
let endpoint = Endpoint::test("lifecycle").expect("test endpoint");
endpoint.retire().expect("retire absent endpoint");
let listener = Listener::bind(&endpoint).expect("bind endpoint");
drop(listener);
endpoint.retire().expect("retire endpoint");
}
#[test]
fn missing_endpoint_probe_reports_a_stable_not_found_or_refused_error() {
let endpoint = Endpoint::test("probe-missing").expect("test endpoint");
let error = Stream::connect(&endpoint).expect_err("missing endpoint must fail to connect");
assert!(matches!(
error.kind(),
std::io::ErrorKind::NotFound | std::io::ErrorKind::ConnectionRefused
));
}
#[test]
fn missing_endpoint_is_reported_stale() {
let endpoint = Endpoint::test("probe-missing-stale").expect("test endpoint");
assert!(endpoint.is_stale());
assert!(!endpoint.target_exists().expect("probe a missing endpoint"));
}
#[test]
fn bound_endpoint_is_not_reported_stale() {
let endpoint = Endpoint::test("probe-live-stale").expect("test endpoint");
let listener = Listener::bind(&endpoint).expect("bind");
assert!(!endpoint.is_stale());
let probed = endpoint.clone();
let (answer_tx, answer_rx) = std::sync::mpsc::channel();
let prober = std::thread::spawn(move || {
let _ = answer_tx.send(probed.target_exists().map_err(|error| error.to_string()));
});
let exists = answer_rx
.recv_timeout(std::time::Duration::from_secs(5))
.expect("the second probe answered without waiting for an accept")
.expect("probe a bound endpoint");
assert!(exists);
prober.join().expect("prober thread");
drop(listener);
}
#[cfg(unix)]
#[test]
fn a_departed_server_leaves_a_stale_socket_file_to_retire() {
let endpoint = Endpoint::test("probe-leftover").expect("test endpoint");
let mut listener = Listener::bind(&endpoint).expect("bind");
listener.do_not_reclaim_name_on_drop();
drop(listener);
assert!(endpoint
.target_exists()
.expect("inspect the leftover target"));
assert!(endpoint.is_stale());
endpoint.retire().expect("retire the leftover");
assert!(!endpoint
.target_exists()
.expect("inspect the retired target"));
}
#[cfg(windows)]
#[test]
fn a_departed_server_leaves_no_named_pipe_behind() {
let endpoint = Endpoint::test("probe-leftover").expect("test endpoint");
let listener = Listener::bind(&endpoint).expect("bind");
assert!(endpoint.target_exists().expect("inspect the live target"));
drop(listener);
assert!(!endpoint
.target_exists()
.expect("inspect the vanished target"));
assert!(endpoint.is_stale());
endpoint.retire().expect("retire a vanished endpoint");
}
#[test]
fn live_endpoint_probe_succeeds_without_disturbing_a_later_accept() {
let endpoint = Endpoint::test("probe-live").expect("test endpoint");
let listener = Listener::bind(&endpoint).expect("bind");
let (probe_accepted_tx, probe_accepted_rx) = std::sync::mpsc::channel();
let server = std::thread::spawn(move || {
listener.accept().expect("accept probe connection");
probe_accepted_tx.send(()).expect("report probe accepted");
listener.accept().expect("accept later connection");
});
let probe = Stream::connect(&endpoint).expect("probe connect");
probe_accepted_rx
.recv_timeout(std::time::Duration::from_secs(5))
.expect("server accepted the probe connection");
drop(probe);
let _client = Stream::connect(&endpoint).expect("connect after probe");
server.join().expect("server thread");
}
#[test]
fn sync_bind_accept_connect_and_peer_identity_round_trip() {
let endpoint = Endpoint::test("sync-roundtrip").expect("test endpoint");
let listener = Listener::bind(&endpoint).expect("bind");
let expected_user = current_user_id().expect("current user identity");
let server = std::thread::spawn(move || {
let mut stream = listener.accept().expect("accept");
let peer = stream.peer_identity().expect("peer identity");
assert_eq!(peer.user_id, expected_user);
let mut request = [0_u8; 4];
stream.read_exact(&mut request).expect("read request");
assert_eq!(&request, b"ping");
stream.write_all(b"pong").expect("write response");
});
let mut client = Stream::connect(&endpoint).expect("connect");
client.write_all(b"ping").expect("write request");
let mut response = [0_u8; 4];
client.read_exact(&mut response).expect("read response");
assert_eq!(&response, b"pong");
server.join().expect("server thread");
}
#[cfg(feature = "ipc-async")]
#[tokio::test]
async fn async_bind_accept_connect_and_peer_identity_round_trip() {
use super::{AsyncListener, AsyncStream};
let endpoint = Endpoint::test("async-roundtrip").expect("test endpoint");
let listener = AsyncListener::bind(&endpoint).expect("bind");
let expected_user = current_user_id().expect("current user identity");
let server = tokio::spawn(async move {
let mut stream = listener.accept().await.expect("accept");
let peer = stream.peer_identity().expect("peer identity");
assert_eq!(peer.user_id, expected_user);
let mut request = [0_u8; 4];
stream.read_exact(&mut request).await.expect("read request");
assert_eq!(&request, b"ping");
stream.write_all(b"pong").await.expect("write response");
});
let mut client = AsyncStream::connect(&endpoint).await.expect("connect");
client.write_all(b"ping").await.expect("write request");
let mut response = [0_u8; 4];
client
.read_exact(&mut response)
.await
.expect("read response");
assert_eq!(&response, b"pong");
server.await.expect("server task");
}
#[cfg(feature = "ipc-async")]
#[tokio::test]
async fn async_inherent_methods_round_trip_with_no_extension_trait_import() {
use super::{AsyncListener, AsyncStream};
let endpoint = Endpoint::test("async-inherent-roundtrip").expect("test endpoint");
let listener = AsyncListener::bind(&endpoint).expect("bind");
let server = tokio::spawn(async move {
let mut stream = listener.accept().await.expect("accept");
let mut request = [0_u8; 4];
stream.read_exact(&mut request).await.expect("read request");
assert_eq!(&request, b"ping");
stream.write_all(b"pong").await.expect("write response");
stream.flush().await.expect("flush response");
stream.shutdown().await.expect("shutdown write half");
});
let mut client = AsyncStream::connect(&endpoint).await.expect("connect");
client.write_all(b"ping").await.expect("write request");
client.flush().await.expect("flush request");
let mut response = [0_u8; 4];
let mut filled = 0;
while filled < response.len() {
let read = client
.read(&mut response[filled..])
.await
.expect("read response");
assert_ne!(read, 0, "peer closed before the full response arrived");
filled += read;
}
assert_eq!(&response, b"pong");
server.await.expect("server task");
}
#[cfg(feature = "ipc-async")]
#[tokio::test]
async fn async_into_split_round_trips_across_owned_halves() {
use super::{AsyncListener, AsyncStream};
let endpoint = Endpoint::test("async-split-roundtrip").expect("test endpoint");
let listener = AsyncListener::bind(&endpoint).expect("bind");
let server = tokio::spawn(async move {
let stream = listener.accept().await.expect("accept");
let (mut read_half, mut write_half) = stream.into_split();
let mut request = [0_u8; 4];
read_half
.read_exact(&mut request)
.await
.expect("read request");
assert_eq!(&request, b"ping");
write_half.write_all(b"pong").await.expect("write response");
write_half.flush().await.expect("flush response");
write_half.shutdown().await.expect("shutdown write half");
});
let client = AsyncStream::connect(&endpoint).await.expect("connect");
let (mut client_read, mut client_write) = client.into_split();
client_write
.write_all(b"ping")
.await
.expect("write request");
client_write.flush().await.expect("flush request");
let mut response = [0_u8; 4];
client_read
.read_exact(&mut response)
.await
.expect("read response");
assert_eq!(&response, b"pong");
server.await.expect("server task");
}
}