Skip to main content

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