core_ipc/
transport.rs

1//! Transport abstractions and socket path resolution.
2
3use std::path::PathBuf;
4use tokio::net::UnixStream;
5
6/// Peer credentials obtained from the transport layer.
7#[derive(Debug, Clone)]
8pub struct PeerCredentials {
9    /// Process ID of the peer.
10    pub pid: u32,
11    /// User ID of the peer (Unix). On Windows, this is 0 (use SID instead).
12    pub uid: u32,
13}
14
15impl PeerCredentials {
16    /// Credentials for an in-process subscriber (e.g. the bus host itself).
17    ///
18    /// Not extracted from a socket — uses the current process's own PID.
19    /// UID is set to `u32::MAX` as a sentinel (never matches a real `UCred` check).
20    #[must_use]
21    pub fn in_process() -> Self {
22        Self {
23            pid: std::process::id(),
24            uid: u32::MAX,
25        }
26    }
27}
28
29/// Get the current process's real credentials (PID + UID).
30///
31/// Used to construct the local side of the Noise prologue. Both sides of
32/// the handshake must agree on each other's PID/UID for the prologue to match.
33#[must_use]
34pub fn local_credentials() -> PeerCredentials {
35    PeerCredentials {
36        pid: std::process::id(),
37        uid: current_uid(),
38    }
39}
40
41/// Extract `UCred` (pid/uid) from a connected Unix domain socket.
42///
43/// Uses `SO_PEERCRED` on Linux to retrieve the peer's process credentials.
44/// Returns an error if credentials cannot be extracted -- the caller MUST
45/// reject the connection. There are no fallback defaults.
46///
47/// # Errors
48///
49/// Returns an error if peer credentials cannot be extracted from the socket.
50pub fn extract_ucred(stream: &UnixStream) -> core_types::Result<PeerCredentials> {
51    let cred = stream
52        .peer_cred()
53        .map_err(|e| core_types::Error::Ipc(format!("UCred extraction failed: {e}")))?;
54    let pid = cred
55        .pid()
56        .and_then(|p| u32::try_from(p).ok())
57        .ok_or_else(|| core_types::Error::Ipc("UCred: PID unavailable".into()))?;
58    Ok(PeerCredentials {
59        pid,
60        uid: cred.uid(),
61    })
62}
63
64/// Get the current process's real UID.
65///
66/// Uses `rustix` for safe, zero-unsafe POSIX syscall access.
67#[cfg(unix)]
68fn current_uid() -> u32 {
69    rustix::process::getuid().as_raw()
70}
71
72#[cfg(not(unix))]
73fn current_uid() -> u32 {
74    0
75}
76
77/// Resolve the platform-appropriate IPC socket path.
78///
79/// # Errors
80///
81/// Returns an error if required platform directories cannot be determined
82/// (e.g. `XDG_RUNTIME_DIR` unset on Linux, no home directory on macOS).
83pub fn socket_path() -> core_types::Result<PathBuf> {
84    #[cfg(target_os = "linux")]
85    {
86        let runtime = std::env::var("XDG_RUNTIME_DIR").map_err(|_| {
87            core_types::Error::Platform(
88                "XDG_RUNTIME_DIR is not set; cannot determine IPC socket path".into(),
89            )
90        })?;
91        Ok(PathBuf::from(runtime).join("pds/bus.sock"))
92    }
93
94    #[cfg(target_os = "macos")]
95    {
96        let home = dirs::home_dir()
97            .ok_or_else(|| core_types::Error::Platform("cannot determine home directory".into()))?;
98        Ok(home.join("Library/Application Support/pds/bus.sock"))
99    }
100
101    #[cfg(target_os = "windows")]
102    {
103        Ok(PathBuf::from(r"\\.\pipe\pds\bus"))
104    }
105
106    #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
107    {
108        Err(core_types::Error::Platform(
109            "unsupported platform: cannot determine IPC socket path".into(),
110        ))
111    }
112}