#[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 {
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 {
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, IpcAsyncStream as AsyncStream,
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 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};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
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");
}
}