hyper_client_sockets/
uri.rs

1#[cfg(any(feature = "unix", feature = "vsock", feature = "firecracker"))]
2use hex::FromHex;
3#[cfg(any(feature = "unix", feature = "vsock", feature = "firecracker"))]
4use http::{uri::InvalidUri, Uri};
5#[cfg(any(feature = "unix", feature = "firecracker"))]
6use std::path::{Path, PathBuf};
7
8/// An extension trait for a URI that allows constructing a hex-encoded Unix socket URI.
9#[cfg(feature = "unix")]
10#[cfg_attr(docsrs, doc(cfg(feature = "unix")))]
11pub trait UnixUri {
12    /// Create a new Unix URI with the given socket path and in-socket URI.
13    fn unix(socket_path: impl AsRef<Path>, url: impl AsRef<str>) -> Result<Uri, InvalidUri>;
14
15    /// Try to deconstruct this Unix URI's socket path.
16    fn parse_unix(&self) -> Result<PathBuf, std::io::Error>;
17}
18
19#[cfg(feature = "unix")]
20#[cfg_attr(docsrs, doc(cfg(feature = "unix")))]
21impl UnixUri for Uri {
22    fn unix(socket_path: impl AsRef<Path>, url: impl AsRef<str>) -> Result<Uri, InvalidUri> {
23        let host = hex::encode(socket_path.as_ref().to_string_lossy().to_string());
24        let uri_str = format!("unix://{host}/{}", url.as_ref().trim_start_matches('/'));
25        let uri = uri_str.parse::<Uri>()?;
26        Ok(uri)
27    }
28
29    fn parse_unix(&self) -> Result<PathBuf, std::io::Error> {
30        if self.scheme_str() != Some("unix") {
31            return Err(io_input_err("URI scheme on a Unix socket must be unix://"));
32        }
33
34        match self.host() {
35            Some(host) => {
36                let bytes = Vec::from_hex(host).map_err(|_| io_input_err("URI host must be hex"))?;
37                Ok(PathBuf::from(String::from_utf8_lossy(&bytes).into_owned()))
38            }
39            None => Err(io_input_err("URI host must be present")),
40        }
41    }
42}
43
44/// An extension trait for hyper URI that allows constructing a hex-encoded virtio-vsock socket URI.
45#[cfg(feature = "vsock")]
46#[cfg_attr(docsrs, doc(cfg(feature = "vsock")))]
47pub trait VsockUri {
48    /// Create a new vsock URI with the given vsock address and in-socket URL
49    fn vsock(addr: vsock::VsockAddr, url: impl AsRef<str>) -> Result<Uri, InvalidUri>;
50
51    /// Deconstruct this vsock URI into its address.
52    fn parse_vsock(&self) -> Result<vsock::VsockAddr, std::io::Error>;
53}
54
55#[cfg(feature = "vsock")]
56#[cfg_attr(docsrs, doc(cfg(feature = "vsock")))]
57impl VsockUri for Uri {
58    fn vsock(addr: vsock::VsockAddr, url: impl AsRef<str>) -> Result<Uri, InvalidUri> {
59        let host = hex::encode(format!("{}.{}", addr.cid(), addr.port()));
60        let uri_str = format!("vsock://{host}/{}", url.as_ref().trim_start_matches('/'));
61        let uri = uri_str.parse::<Uri>()?;
62        Ok(uri)
63    }
64
65    fn parse_vsock(&self) -> Result<vsock::VsockAddr, std::io::Error> {
66        if self.scheme_str() != Some("vsock") {
67            return Err(io_input_err("URI scheme on a vsock socket must be vsock://"));
68        }
69
70        match self.host() {
71            Some(host) => {
72                let full_str = Vec::from_hex(host)
73                    .map_err(|_| io_input_err("URI host must be hex"))
74                    .map(|bytes| String::from_utf8_lossy(&bytes).into_owned())?;
75                let splits = full_str
76                    .split_once('.')
77                    .ok_or_else(|| io_input_err("URI host could not be split at . into 2 slices (CID, then port)"))?;
78                let cid: u32 = splits
79                    .0
80                    .parse()
81                    .map_err(|_| io_input_err("First split of URI (CID) can't be parsed"))?;
82                let port: u32 = splits
83                    .1
84                    .parse()
85                    .map_err(|_| io_input_err("Second split of URI (port) can't be parsed"))?;
86
87                Ok(vsock::VsockAddr::new(cid, port))
88            }
89            None => Err(io_input_err("URI host must be present")),
90        }
91    }
92}
93
94/// An extension trait for hyper URI that allows constructing a hex-encoded Firecracker socket URI.
95#[cfg(feature = "firecracker")]
96#[cfg_attr(docsrs, doc(cfg(feature = "firecracker")))]
97pub trait FirecrackerUri {
98    /// Create a new Firecracker URI with the given host socket path, guest port and in-socket URL
99    fn firecracker(
100        host_socket_path: impl AsRef<Path>,
101        guest_port: u32,
102        url: impl AsRef<str>,
103    ) -> Result<Uri, InvalidUri>;
104
105    /// Deconstruct this Firecracker URI into its host socket path and guest port
106    fn parse_firecracker(&self) -> Result<(PathBuf, u32), std::io::Error>;
107}
108
109#[cfg(feature = "firecracker")]
110#[cfg_attr(docsrs, doc(cfg(feature = "firecracker")))]
111impl FirecrackerUri for Uri {
112    fn firecracker(
113        host_socket_path: impl AsRef<Path>,
114        guest_port: u32,
115        url: impl AsRef<str>,
116    ) -> Result<Uri, InvalidUri> {
117        let host = hex::encode(format!(
118            "{}:{guest_port}",
119            host_socket_path.as_ref().to_string_lossy().to_string()
120        ));
121        let uri_str = format!("fc://{host}/{}", url.as_ref().trim_start_matches('/'));
122        let uri = uri_str.parse::<Uri>()?;
123        Ok(uri)
124    }
125
126    fn parse_firecracker(&self) -> Result<(PathBuf, u32), std::io::Error> {
127        if self.scheme_str() != Some("fc") {
128            return Err(io_input_err("URI scheme on a Firecracker socket must be fc://"));
129        }
130
131        let host = self.host().ok_or_else(|| io_input_err("URI host must be present"))?;
132        let hex_decoded = Vec::from_hex(host).map_err(|_| io_input_err("URI host must be hex"))?;
133        let full_str = String::from_utf8_lossy(&hex_decoded).into_owned();
134        let splits = full_str
135            .split_once(':')
136            .ok_or_else(|| io_input_err("URI host could not be split in halves with a ."))?;
137        let host_socket_path = PathBuf::try_from(splits.0)
138            .map_err(|_| io_input_err("URI socket path could not be converted to a path"))?;
139        let guest_port: u32 = splits
140            .1
141            .parse()
142            .map_err(|_| io_input_err("URI guest port could not converted to u32"))?;
143
144        Ok((host_socket_path, guest_port))
145    }
146}
147
148#[cfg(any(feature = "unix", feature = "vsock", feature = "firecracker"))]
149#[inline(always)]
150fn io_input_err(detail: &str) -> std::io::Error {
151    std::io::Error::new(std::io::ErrorKind::InvalidInput, detail)
152}
153
154#[cfg(test)]
155mod tests {
156    use std::path::PathBuf;
157
158    use hyper::Uri;
159    use vsock::VsockAddr;
160
161    use crate::uri::{FirecrackerUri, UnixUri, VsockUri};
162
163    #[test]
164    fn unix_uri_should_be_constructed_correctly() {
165        let uri_str = format!("unix://{}/route", hex::encode("/tmp/socket.sock"));
166        assert_eq!(
167            Uri::unix("/tmp/socket.sock", "/route").unwrap(),
168            uri_str.parse::<Uri>().unwrap()
169        );
170    }
171
172    #[test]
173    fn unix_uri_should_be_deconstructed_correctly() {
174        let uri = format!("unix://{}/route", hex::encode("/tmp/socket.sock"));
175        assert_eq!(
176            uri.parse::<Uri>().unwrap().parse_unix().unwrap(),
177            PathBuf::from("/tmp/socket.sock")
178        );
179    }
180
181    #[test]
182    fn vsock_uri_should_be_constructed_correctly() {
183        let uri = format!("vsock://{}/route", hex::encode("10.20"));
184        assert_eq!(
185            uri.parse::<Uri>().unwrap(),
186            Uri::vsock(VsockAddr::new(10, 20), "/route").unwrap()
187        );
188    }
189
190    #[test]
191    fn vsock_uri_should_be_deconstructed_correctly() {
192        let uri = format!("vsock://{}/route", hex::encode("10.20"))
193            .parse::<Uri>()
194            .unwrap();
195        assert_eq!(uri.parse_vsock().unwrap(), VsockAddr::new(10, 20));
196    }
197
198    #[test]
199    fn firecracker_uri_should_be_constructed_correctly() {
200        let uri_str = format!("fc://{}/route", hex::encode("/tmp/socket.sock:1000"));
201        assert_eq!(
202            Uri::firecracker("/tmp/socket.sock", 1000, "/route").unwrap(),
203            uri_str.parse::<Uri>().unwrap()
204        );
205    }
206
207    #[test]
208    fn firecracker_uri_should_be_deconstructed_correctly() {
209        let uri = format!("fc://{}/route", hex::encode("/tmp/socket.sock:1000"))
210            .parse::<Uri>()
211            .unwrap();
212        let (socket_path, port) = uri.parse_firecracker().unwrap();
213        assert_eq!(socket_path, PathBuf::from("/tmp/socket.sock"));
214        assert_eq!(port, 1000);
215    }
216}