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 | |