hyper_client_sockets/
uri.rs1#[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#[cfg(feature = "unix")]
10#[cfg_attr(docsrs, doc(cfg(feature = "unix")))]
11pub trait UnixUri {
12 fn unix(socket_path: impl AsRef<Path>, url: impl AsRef<str>) -> Result<Uri, InvalidUri>;
14
15 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#[cfg(feature = "vsock")]
46#[cfg_attr(docsrs, doc(cfg(feature = "vsock")))]
47pub trait VsockUri {
48 fn vsock(addr: vsock::VsockAddr, url: impl AsRef<str>) -> Result<Uri, InvalidUri>;
50
51 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#[cfg(feature = "firecracker")]
96#[cfg_attr(docsrs, doc(cfg(feature = "firecracker")))]
97pub trait FirecrackerUri {
98 fn firecracker(
100 host_socket_path: impl AsRef<Path>,
101 guest_port: u32,
102 url: impl AsRef<str>,
103 ) -> Result<Uri, InvalidUri>;
104
105 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}