| 1 | //! D-Bus transport Information module. |
| 2 | //! |
| 3 | //! This module provides the trasport information for D-Bus addresses. |
| 4 | |
| 5 | #[cfg (windows)] |
| 6 | use crate::win32::autolaunch_bus_address; |
| 7 | use crate::{Error, Result}; |
| 8 | #[cfg (not(feature = "tokio" ))] |
| 9 | use async_io::Async; |
| 10 | use std::collections::HashMap; |
| 11 | #[cfg (not(feature = "tokio" ))] |
| 12 | use std::net::TcpStream; |
| 13 | #[cfg (unix)] |
| 14 | use std::os::unix::net::{SocketAddr, UnixStream}; |
| 15 | #[cfg (feature = "tokio" )] |
| 16 | use tokio::net::TcpStream; |
| 17 | #[cfg (feature = "tokio-vsock" )] |
| 18 | use tokio_vsock::VsockStream; |
| 19 | #[cfg (windows)] |
| 20 | use uds_windows::UnixStream; |
| 21 | #[cfg (all(feature = "vsock" , not(feature = "tokio" )))] |
| 22 | use vsock::VsockStream; |
| 23 | |
| 24 | use std::{ |
| 25 | fmt::{Display, Formatter}, |
| 26 | str::from_utf8_unchecked, |
| 27 | }; |
| 28 | |
| 29 | mod unix; |
| 30 | pub use unix::{Unix, UnixSocket}; |
| 31 | mod tcp; |
| 32 | pub use tcp::{Tcp, TcpTransportFamily}; |
| 33 | #[cfg (windows)] |
| 34 | mod autolaunch; |
| 35 | #[cfg (windows)] |
| 36 | pub use autolaunch::{Autolaunch, AutolaunchScope}; |
| 37 | #[cfg (target_os = "macos" )] |
| 38 | mod launchd; |
| 39 | #[cfg (target_os = "macos" )] |
| 40 | pub 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. |
| 47 | mod vsock_transport; |
| 48 | #[cfg (target_os = "linux" )] |
| 49 | use std::os::linux::net::SocketAddrExt; |
| 50 | #[cfg (any( |
| 51 | all(feature = "vsock" , not(feature = "tokio" )), |
| 52 | feature = "tokio-vsock" |
| 53 | ))] |
| 54 | pub use vsock_transport::Vsock; |
| 55 | |
| 56 | /// The transport properties of a D-Bus address. |
| 57 | #[derive (Clone, Debug, PartialEq, Eq)] |
| 58 | #[non_exhaustive ] |
| 59 | pub 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 | |
| 82 | impl 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)] |
| 235 | pub(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)] |
| 244 | pub(crate) enum Stream { |
| 245 | #[cfg (unix)] |
| 246 | Unix(tokio::net::UnixStream), |
| 247 | Tcp(TcpStream), |
| 248 | #[cfg (feature = "tokio-vsock" )] |
| 249 | Vsock(VsockStream), |
| 250 | } |
| 251 | |
| 252 | fn 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 | |
| 264 | pub(crate) fn decode_percents(value: &str) -> Result<Vec<u8>> { |
| 265 | let mut iter: Chars<'_> = value.chars(); |
| 266 | let mut decoded: Vec = Vec::new(); |
| 267 | |
| 268 | while let Some(c: char) = 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 | |
| 289 | pub(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 | |
| 332 | impl Display for Transport { |
| 333 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { |
| 334 | match self { |
| 335 | Self::Tcp(tcp: &Tcp) => write!(f, " {}" , tcp)?, |
| 336 | Self::Unix(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 | } |
| 351 | |