zbus/address/transport/
mod.rs

1//! D-Bus transport Information module.
2//!
3//! This module provides the transport information for D-Bus addresses.
4
5#[cfg(unix)]
6use crate::connection::socket::Command;
7#[cfg(windows)]
8use crate::win32::autolaunch_bus_address;
9use crate::{Error, Result};
10#[cfg(not(feature = "tokio"))]
11use async_io::Async;
12use std::collections::HashMap;
13#[cfg(not(feature = "tokio"))]
14use std::net::TcpStream;
15#[cfg(unix)]
16use std::os::unix::net::{SocketAddr, UnixStream};
17#[cfg(feature = "tokio")]
18use tokio::net::TcpStream;
19#[cfg(feature = "tokio-vsock")]
20use tokio_vsock::VsockStream;
21#[cfg(windows)]
22use uds_windows::UnixStream;
23#[cfg(all(feature = "vsock", not(feature = "tokio")))]
24use vsock::VsockStream;
25#[cfg(unix)]
26mod unixexec;
27#[cfg(unix)]
28pub use unixexec::Unixexec;
29
30use std::{
31    fmt::{Display, Formatter},
32    str::from_utf8_unchecked,
33};
34
35mod unix;
36pub use unix::{Unix, UnixSocket};
37mod tcp;
38pub use tcp::{Tcp, TcpTransportFamily};
39#[cfg(windows)]
40mod autolaunch;
41#[cfg(windows)]
42pub use autolaunch::{Autolaunch, AutolaunchScope};
43#[cfg(target_os = "macos")]
44mod launchd;
45#[cfg(target_os = "macos")]
46pub use launchd::Launchd;
47#[cfg(any(
48    all(feature = "vsock", not(feature = "tokio")),
49    feature = "tokio-vsock"
50))]
51#[path = "vsock.rs"]
52// Gotta rename to avoid name conflict with the `vsock` crate.
53mod vsock_transport;
54#[cfg(target_os = "linux")]
55use std::os::linux::net::SocketAddrExt;
56#[cfg(any(
57    all(feature = "vsock", not(feature = "tokio")),
58    feature = "tokio-vsock"
59))]
60pub use vsock_transport::Vsock;
61
62/// The transport properties of a D-Bus address.
63#[derive(Clone, Debug, PartialEq, Eq)]
64#[non_exhaustive]
65pub enum Transport {
66    /// A Unix Domain Socket address.
67    Unix(Unix),
68    /// A TCP address.
69    Tcp(Tcp),
70    /// An autolaunch D-Bus address.
71    #[cfg(windows)]
72    Autolaunch(Autolaunch),
73    /// A launchd D-Bus address.
74    #[cfg(target_os = "macos")]
75    Launchd(Launchd),
76    #[cfg(any(
77        all(feature = "vsock", not(feature = "tokio")),
78        feature = "tokio-vsock"
79    ))]
80    /// A VSOCK address.
81    ///
82    /// This variant is only available when either the `vsock` or `tokio-vsock` feature is enabled.
83    /// The type of `stream` is `vsock::VsockStream` with the `vsock` feature and
84    /// `tokio_vsock::VsockStream` with the `tokio-vsock` feature.
85    Vsock(Vsock),
86    /// A `unixexec` address.
87    #[cfg(unix)]
88    Unixexec(Unixexec),
89}
90
91impl Transport {
92    #[cfg_attr(any(target_os = "macos", windows), async_recursion::async_recursion)]
93    pub(super) async fn connect(self) -> Result<Stream> {
94        match self {
95            Transport::Unix(unix) => {
96                // This is a `path` in case of Windows until uds_windows provides the needed API:
97                // https://github.com/haraldh/rust_uds_windows/issues/14
98                let addr = match unix.take_path() {
99                    #[cfg(unix)]
100                    UnixSocket::File(path) => SocketAddr::from_pathname(path)?,
101                    #[cfg(windows)]
102                    UnixSocket::File(path) => path,
103                    #[cfg(target_os = "linux")]
104                    UnixSocket::Abstract(name) => {
105                        SocketAddr::from_abstract_name(name.as_encoded_bytes())?
106                    }
107                    UnixSocket::Dir(_) | UnixSocket::TmpDir(_) => {
108                        // you can't connect to a unix:dir
109                        return Err(Error::Unsupported);
110                    }
111                };
112                let stream = crate::Task::spawn_blocking(
113                    move || -> Result<_> {
114                        #[cfg(unix)]
115                        let stream = UnixStream::connect_addr(&addr)?;
116                        #[cfg(windows)]
117                        let stream = UnixStream::connect(addr)?;
118                        stream.set_nonblocking(true)?;
119
120                        Ok(stream)
121                    },
122                    "unix stream connection",
123                )
124                .await?;
125                #[cfg(not(feature = "tokio"))]
126                {
127                    Async::new(stream)
128                        .map(Stream::Unix)
129                        .map_err(|e| Error::InputOutput(e.into()))
130                }
131
132                #[cfg(feature = "tokio")]
133                {
134                    #[cfg(unix)]
135                    {
136                        tokio::net::UnixStream::from_std(stream)
137                            .map(Stream::Unix)
138                            .map_err(|e| Error::InputOutput(e.into()))
139                    }
140
141                    #[cfg(not(unix))]
142                    {
143                        let _ = stream;
144                        Err(Error::Unsupported)
145                    }
146                }
147            }
148            #[cfg(unix)]
149            Transport::Unixexec(unixexec) => unixexec.connect().await.map(Stream::Unixexec),
150            #[cfg(all(feature = "vsock", not(feature = "tokio")))]
151            Transport::Vsock(addr) => {
152                let stream = VsockStream::connect_with_cid_port(addr.cid(), addr.port())?;
153                Async::new(stream).map(Stream::Vsock).map_err(Into::into)
154            }
155
156            #[cfg(feature = "tokio-vsock")]
157            Transport::Vsock(addr) => {
158                VsockStream::connect(tokio_vsock::VsockAddr::new(addr.cid(), addr.port()))
159                    .await
160                    .map(Stream::Vsock)
161                    .map_err(Into::into)
162            }
163
164            Transport::Tcp(mut addr) => match addr.take_nonce_file() {
165                Some(nonce_file) => {
166                    #[allow(unused_mut)]
167                    let mut stream = addr.connect().await?;
168
169                    #[cfg(unix)]
170                    let nonce_file = {
171                        use std::os::unix::ffi::OsStrExt;
172                        std::ffi::OsStr::from_bytes(&nonce_file)
173                    };
174
175                    #[cfg(windows)]
176                    let nonce_file = std::str::from_utf8(&nonce_file).map_err(|_| {
177                        Error::Address("nonce file path is invalid UTF-8".to_owned())
178                    })?;
179
180                    #[cfg(not(feature = "tokio"))]
181                    {
182                        let nonce = std::fs::read(nonce_file)?;
183                        let mut nonce = &nonce[..];
184
185                        while !nonce.is_empty() {
186                            let len = stream
187                                .write_with(|mut s| std::io::Write::write(&mut s, nonce))
188                                .await?;
189                            nonce = &nonce[len..];
190                        }
191                    }
192
193                    #[cfg(feature = "tokio")]
194                    {
195                        let nonce = tokio::fs::read(nonce_file).await?;
196                        tokio::io::AsyncWriteExt::write_all(&mut stream, &nonce).await?;
197                    }
198
199                    Ok(Stream::Tcp(stream))
200                }
201                None => addr.connect().await.map(Stream::Tcp),
202            },
203
204            #[cfg(windows)]
205            Transport::Autolaunch(Autolaunch { scope }) => match scope {
206                Some(_) => Err(Error::Address(
207                    "Autolaunch scopes are currently unsupported".to_owned(),
208                )),
209                None => {
210                    let addr = autolaunch_bus_address()?;
211                    addr.connect().await
212                }
213            },
214
215            #[cfg(target_os = "macos")]
216            Transport::Launchd(launchd) => {
217                let addr = launchd.bus_address().await?;
218                addr.connect().await
219            }
220        }
221    }
222
223    // Helper for `FromStr` impl of `Address`.
224    pub(super) fn from_options(transport: &str, options: HashMap<&str, &str>) -> Result<Self> {
225        match transport {
226            "unix" => Unix::from_options(options).map(Self::Unix),
227            #[cfg(unix)]
228            "unixexec" => Unixexec::from_options(options).map(Self::Unixexec),
229            "tcp" => Tcp::from_options(options, false).map(Self::Tcp),
230            "nonce-tcp" => Tcp::from_options(options, true).map(Self::Tcp),
231            #[cfg(any(
232                all(feature = "vsock", not(feature = "tokio")),
233                feature = "tokio-vsock"
234            ))]
235            "vsock" => Vsock::from_options(options).map(Self::Vsock),
236            #[cfg(windows)]
237            "autolaunch" => Autolaunch::from_options(options).map(Self::Autolaunch),
238            #[cfg(target_os = "macos")]
239            "launchd" => Launchd::from_options(options).map(Self::Launchd),
240
241            _ => Err(Error::Address(format!(
242                "unsupported transport '{transport}'"
243            ))),
244        }
245    }
246}
247
248#[cfg(not(feature = "tokio"))]
249#[derive(Debug)]
250pub(crate) enum Stream {
251    Unix(Async<UnixStream>),
252    #[cfg(unix)]
253    Unixexec(Command),
254    Tcp(Async<TcpStream>),
255    #[cfg(feature = "vsock")]
256    Vsock(Async<VsockStream>),
257}
258
259#[cfg(feature = "tokio")]
260#[derive(Debug)]
261pub(crate) enum Stream {
262    #[cfg(unix)]
263    Unix(tokio::net::UnixStream),
264    #[cfg(unix)]
265    Unixexec(Command),
266    Tcp(TcpStream),
267    #[cfg(feature = "tokio-vsock")]
268    Vsock(VsockStream),
269}
270
271fn decode_hex(c: char) -> Result<u8> {
272    match c {
273        '0'..='9' => Ok(c as u8 - b'0'),
274        'a'..='f' => Ok(c as u8 - b'a' + 10),
275        'A'..='F' => Ok(c as u8 - b'A' + 10),
276
277        _ => Err(Error::Address(
278            "invalid hexadecimal character in percent-encoded sequence".to_owned(),
279        )),
280    }
281}
282
283pub(crate) fn decode_percents(value: &str) -> Result<Vec<u8>> {
284    let mut iter = value.chars();
285    let mut decoded = Vec::new();
286
287    while let Some(c) = iter.next() {
288        if matches!(c, '-' | '0'..='9' | 'A'..='Z' | 'a'..='z' | '_' | '/' | '.' | '\\' | '*') {
289            decoded.push(c as u8)
290        } else if c == '%' {
291            decoded.push(
292                (decode_hex(iter.next().ok_or_else(|| {
293                    Error::Address("incomplete percent-encoded sequence".to_owned())
294                })?)?
295                    << 4)
296                    | decode_hex(iter.next().ok_or_else(|| {
297                        Error::Address("incomplete percent-encoded sequence".to_owned())
298                    })?)?,
299            );
300        } else {
301            return Err(Error::Address("Invalid character in address".to_owned()));
302        }
303    }
304
305    Ok(decoded)
306}
307
308pub(super) fn encode_percents(f: &mut Formatter<'_>, mut value: &[u8]) -> std::fmt::Result {
309    const LOOKUP: &str = "\
310%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f\
311%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f\
312%20%21%22%23%24%25%26%27%28%29%2a%2b%2c%2d%2e%2f\
313%30%31%32%33%34%35%36%37%38%39%3a%3b%3c%3d%3e%3f\
314%40%41%42%43%44%45%46%47%48%49%4a%4b%4c%4d%4e%4f\
315%50%51%52%53%54%55%56%57%58%59%5a%5b%5c%5d%5e%5f\
316%60%61%62%63%64%65%66%67%68%69%6a%6b%6c%6d%6e%6f\
317%70%71%72%73%74%75%76%77%78%79%7a%7b%7c%7d%7e%7f\
318%80%81%82%83%84%85%86%87%88%89%8a%8b%8c%8d%8e%8f\
319%90%91%92%93%94%95%96%97%98%99%9a%9b%9c%9d%9e%9f\
320%a0%a1%a2%a3%a4%a5%a6%a7%a8%a9%aa%ab%ac%ad%ae%af\
321%b0%b1%b2%b3%b4%b5%b6%b7%b8%b9%ba%bb%bc%bd%be%bf\
322%c0%c1%c2%c3%c4%c5%c6%c7%c8%c9%ca%cb%cc%cd%ce%cf\
323%d0%d1%d2%d3%d4%d5%d6%d7%d8%d9%da%db%dc%dd%de%df\
324%e0%e1%e2%e3%e4%e5%e6%e7%e8%e9%ea%eb%ec%ed%ee%ef\
325%f0%f1%f2%f3%f4%f5%f6%f7%f8%f9%fa%fb%fc%fd%fe%ff";
326
327    loop {
328        let pos = value.iter().position(
329            |c| !matches!(c, b'-' | b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'_' | b'/' | b'.' | b'\\' | b'*'),
330        );
331
332        if let Some(pos) = pos {
333            // SAFETY: The above `position()` call made sure that only ASCII chars are in the string
334            // up to `pos`
335            f.write_str(unsafe { from_utf8_unchecked(&value[..pos]) })?;
336
337            let c = value[pos];
338            value = &value[pos + 1..];
339
340            let pos = c as usize * 3;
341            f.write_str(&LOOKUP[pos..pos + 3])?;
342        } else {
343            // SAFETY: The above `position()` call made sure that only ASCII chars are in the rest
344            // of the string
345            f.write_str(unsafe { from_utf8_unchecked(value) })?;
346            return Ok(());
347        }
348    }
349}
350
351impl Display for Transport {
352    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
353        match self {
354            Self::Tcp(tcp) => write!(f, "{tcp}")?,
355            Self::Unix(unix) => write!(f, "{unix}")?,
356            #[cfg(unix)]
357            Self::Unixexec(unixexec) => write!(f, "{unixexec}")?,
358            #[cfg(any(
359                all(feature = "vsock", not(feature = "tokio")),
360                feature = "tokio-vsock"
361            ))]
362            Self::Vsock(vsock) => write!(f, "{}", vsock)?,
363            #[cfg(windows)]
364            Self::Autolaunch(autolaunch) => write!(f, "{autolaunch}")?,
365            #[cfg(target_os = "macos")]
366            Self::Launchd(launchd) => write!(f, "{launchd}")?,
367        }
368
369        Ok(())
370    }
371}