zbus/address/transport/
mod.rs

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