Skip to content

Commit 2ec077c

Browse files
committed
feat(firecracker,unix): unix::SocketAddr methods for Uri extensions
1 parent 11982ad commit 2ec077c

File tree

10 files changed

+602
-210
lines changed

10 files changed

+602
-210
lines changed

src/uri/firecracker.rs

Lines changed: 0 additions & 65 deletions
This file was deleted.

src/uri/firecracker/mod.rs

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
#[cfg(test)]
2+
mod tests;
3+
4+
use std::{
5+
ffi::OsString,
6+
io::Result as IoResult,
7+
os::unix::{ffi::OsStringExt as _, net::SocketAddr as UnixSocketAddr},
8+
path::{Path, PathBuf},
9+
};
10+
11+
#[cfg(target_os = "android")]
12+
use std::os::android::net::SocketAddrExt as _;
13+
14+
#[cfg(target_os = "linux")]
15+
use std::os::linux::net::SocketAddrExt as _;
16+
17+
use hex::{encode, FromHex};
18+
use http::uri::{InvalidUri, Uri};
19+
20+
use super::io_input_err;
21+
22+
/// An extension trait for hyper URI that allows constructing a hex-encoded Firecracker socket URI.
23+
pub trait FirecrackerUri {
24+
/// Create a new Firecracker URI with the given host socket path, guest port and in-socket URL.
25+
fn firecracker<P: AsRef<Path>, S: AsRef<str>>(
26+
host_socket_path: P,
27+
guest_port: u32,
28+
url: S,
29+
) -> Result<Uri, InvalidUri>;
30+
31+
/// Create a new Firecracker URI with the given host socket address, guest port and in-socket URL.
32+
fn firecracker_addr<S: AsRef<str>>(
33+
host_socket_addr: UnixSocketAddr,
34+
guest_port: u32,
35+
url: S,
36+
) -> Result<Uri, InvalidUri>;
37+
38+
/// Deconstruct this Firecracker URI into its host socket path and guest port.
39+
fn parse_firecracker(&self) -> IoResult<(PathBuf, u32)>;
40+
41+
/// Deconstruct this Firecracker URI into its host socket address and guest port.
42+
fn parse_firecracker_addr(&self) -> IoResult<(UnixSocketAddr, u32)> {
43+
let (path, port) = self.parse_firecracker()?;
44+
45+
#[cfg(any(target_os = "android", target_os = "linux"))]
46+
let from_abstract_name = |err| {
47+
let octets = path.as_os_str().as_encoded_bytes();
48+
match octets.split_first() {
49+
Some((0, name)) => UnixSocketAddr::from_abstract_name(name),
50+
_ => Err(err),
51+
}
52+
};
53+
54+
#[cfg(not(any(target_os = "android", target_os = "linux")))]
55+
let from_abstract_name = |err| Err(err);
56+
57+
UnixSocketAddr::from_pathname(&path)
58+
.or_else(from_abstract_name)
59+
.map(|address| (address, port))
60+
}
61+
}
62+
63+
fn from_octets<S>(prefix: &str, octets: &[u8], guest_port: u32, url: S) -> Result<Uri, InvalidUri>
64+
where
65+
S: AsRef<str>,
66+
{
67+
let host = encode(octets);
68+
let guest_port = encode(guest_port.to_string());
69+
let authority = format!("{prefix}{host}{:02x}{guest_port}", b':');
70+
let path_and_query = url.as_ref().trim_start_matches('/');
71+
let uri_str = format!("fc://{authority}/{path_and_query}");
72+
uri_str.parse()
73+
}
74+
75+
impl FirecrackerUri for Uri {
76+
fn firecracker<P, S>(host_socket_path: P, guest_port: u32, url: S) -> Result<Uri, InvalidUri>
77+
where
78+
P: AsRef<Path>,
79+
S: AsRef<str>,
80+
{
81+
let octets = host_socket_path.as_ref().as_os_str().as_encoded_bytes();
82+
from_octets("", octets, guest_port, url)
83+
}
84+
85+
fn firecracker_addr<S>(host_socket_addr: UnixSocketAddr, guest_port: u32, url: S) -> Result<Uri, InvalidUri>
86+
where
87+
S: AsRef<str>,
88+
{
89+
let (prefix, octets) = match host_socket_addr.as_pathname() {
90+
Some(host_socket_path) => ("", host_socket_path.as_os_str().as_encoded_bytes()),
91+
92+
None => {
93+
#[cfg(any(target_os = "android", target_os = "linux"))]
94+
let octets = host_socket_addr.as_abstract_name().unwrap_or_default();
95+
96+
#[cfg(not(any(target_os = "android", target_os = "linux")))]
97+
let octets = &[];
98+
99+
// Unnamed Unix Domain sockets are encoded as `00`:
100+
("00", octets)
101+
}
102+
};
103+
from_octets(prefix, octets, guest_port, url)
104+
}
105+
106+
fn parse_firecracker(&self) -> IoResult<(PathBuf, u32)> {
107+
if self.scheme_str() == Some("fc") {
108+
let host_hex = self.host().ok_or_else(|| io_input_err("URI host must be present"))?;
109+
let mut host_octets =
110+
Vec::from_hex(host_hex).map_err(|_| io_input_err("URI host must be hexadecimal encoded"))?;
111+
112+
let colon_pos = host_octets
113+
.iter()
114+
.rposition(|octet| *octet == b':')
115+
.ok_or_else(|| io_input_err("URI host does not encode port"))?;
116+
117+
let guest_port = String::from_utf8(host_octets.split_off(colon_pos))
118+
.map_err(|_| io_input_err("URI guest port is not valid UTF8"))?
119+
.split_at(1)
120+
.1
121+
.parse::<u32>()
122+
.map_err(|_| io_input_err("URI guest port could not be parsed"))?;
123+
124+
let host_socket_path = OsString::from_vec(host_octets).into();
125+
126+
Ok((host_socket_path, guest_port))
127+
} else {
128+
Err(io_input_err("URI scheme on a Firecracker socket must be fc://"))
129+
}
130+
}
131+
}

src/uri/firecracker/tests.rs

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
use std::{fmt::Debug, os::unix::net::SocketAddr as UnixSocketAddr, path::PathBuf};
2+
3+
#[cfg(target_os = "android")]
4+
use std::os::android::net::SocketAddrExt as _;
5+
6+
#[cfg(target_os = "linux")]
7+
use std::os::linux::net::SocketAddrExt as _;
8+
9+
use super::FirecrackerUri as _;
10+
use hyper::Uri;
11+
12+
fn assert_debug_eq<E, T>(expected: E, value: T)
13+
where
14+
E: Debug,
15+
T: Debug,
16+
{
17+
assert_eq!(format!("{expected:?}"), format!("{value:?}"))
18+
}
19+
20+
#[cfg(any(target_os = "android", target_os = "linux"))]
21+
#[test]
22+
fn decode_abstract_name_address() {
23+
// TODO: Randomise:
24+
let abstract_name = "abstract";
25+
let port = 1000;
26+
let path_and_query = "/route";
27+
28+
let uri = {
29+
let formatted = format!("{abstract_name}:{port}");
30+
let uri_str = format!("fc://00{}{path_and_query}", hex::encode(formatted));
31+
uri_str.parse::<Uri>().unwrap()
32+
};
33+
34+
let expected = {
35+
let address = UnixSocketAddr::from_abstract_name(abstract_name).unwrap();
36+
(address, port)
37+
};
38+
39+
assert_debug_eq(expected, uri.parse_firecracker_addr().unwrap());
40+
}
41+
42+
#[test]
43+
fn decode_abstract_name_path() {
44+
// TODO: Randomise:
45+
let abstract_name = "\0abstract";
46+
let port = 1000;
47+
let path_and_query = "/route";
48+
49+
let uri = {
50+
let formatted = format!("{abstract_name}:{port}");
51+
let uri_str = format!("fc://{}{path_and_query}", hex::encode(formatted));
52+
uri_str.parse::<Uri>().unwrap()
53+
};
54+
55+
let expected = {
56+
let path = PathBuf::from(abstract_name);
57+
(path, port)
58+
};
59+
60+
assert_eq!(expected, uri.parse_firecracker().unwrap());
61+
}
62+
63+
#[test]
64+
fn decode_pathname_address() {
65+
// TODO: Randomise:
66+
let path = "/tmp/socket.sock";
67+
let port = 1000;
68+
let path_and_query = "/route";
69+
70+
let uri = {
71+
let formatted = format!("{path}:{port}");
72+
let uri_str = format!("fc://{}{path_and_query}", hex::encode(formatted));
73+
uri_str.parse::<Uri>().unwrap()
74+
};
75+
76+
let expected = {
77+
let address = UnixSocketAddr::from_pathname(path).unwrap();
78+
(address, port)
79+
};
80+
81+
assert_debug_eq(expected, uri.parse_firecracker_addr().unwrap());
82+
}
83+
84+
#[test]
85+
fn decode_pathname_path() {
86+
// TODO: Randomise:
87+
let path = "/tmp/socket.sock";
88+
let port = 1000;
89+
let path_and_query = "/route";
90+
91+
let uri = {
92+
let formatted = format!("{path}:{port}");
93+
let uri_str = format!("fc://{}{path_and_query}", hex::encode(formatted));
94+
uri_str.parse::<Uri>().unwrap()
95+
};
96+
97+
let expected = {
98+
let path = PathBuf::from(path);
99+
(path, port)
100+
};
101+
102+
assert_eq!(expected, uri.parse_firecracker().unwrap());
103+
}
104+
105+
#[cfg(any(target_os = "android", target_os = "linux"))]
106+
#[test]
107+
fn encode_abstract_name_as_address() {
108+
// TODO: Randomise:
109+
let abstract_name = "abstract";
110+
let port = 1000;
111+
let path_and_query = "/route";
112+
113+
let address = UnixSocketAddr::from_abstract_name(abstract_name).unwrap();
114+
115+
let expected = {
116+
let formatted = format!("{abstract_name}:{port}");
117+
let uri_str = format!("fc://00{}{path_and_query}", hex::encode(formatted));
118+
uri_str.parse::<Uri>().unwrap()
119+
};
120+
121+
assert_eq!(expected, Uri::firecracker_addr(address, port, path_and_query).unwrap());
122+
}
123+
124+
#[test]
125+
fn encode_abstract_name_as_path() {
126+
// TODO: Randomise:
127+
let abstract_name = "\0abstract";
128+
let port = 1000;
129+
let path_and_query = "/route";
130+
131+
let expected = {
132+
let formatted = format!("{abstract_name}:{port}");
133+
let uri_str = format!("fc://{}{path_and_query}", hex::encode(formatted));
134+
uri_str.parse::<Uri>().unwrap()
135+
};
136+
137+
assert_eq!(expected, Uri::firecracker(abstract_name, port, path_and_query).unwrap());
138+
}
139+
140+
#[test]
141+
fn encode_pathname_as_address() {
142+
// TODO: Randomise:
143+
let path = "/tmp/socket.sock";
144+
let port = 1000;
145+
let path_and_query = "/route";
146+
147+
let address = UnixSocketAddr::from_pathname(path).unwrap();
148+
149+
let expected = {
150+
let formatted = format!("{path}:{port}");
151+
let uri_str = format!("fc://{}{path_and_query}", hex::encode(formatted));
152+
uri_str.parse::<Uri>().unwrap()
153+
};
154+
155+
assert_eq!(expected, Uri::firecracker_addr(address, port, path_and_query).unwrap());
156+
}
157+
158+
#[test]
159+
fn encode_pathname_as_path() {
160+
// TODO: Randomise:
161+
let path = "/tmp/socket.sock";
162+
let port = 1000;
163+
let path_and_query = "/route";
164+
165+
let expected = {
166+
let formatted = format!("{path}:{port}");
167+
let uri_str = format!("fc://{}{path_and_query}", hex::encode(formatted));
168+
uri_str.parse::<Uri>().unwrap()
169+
};
170+
171+
assert_eq!(expected, Uri::firecracker(path, port, path_and_query).unwrap());
172+
}

src/uri/mod.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
#[cfg(test)]
2-
mod tests;
3-
41
#[cfg(feature = "firecracker")]
52
#[cfg_attr(docsrs, doc(cfg(feature = "firecracker")))]
63
pub mod firecracker;

0 commit comments

Comments
 (0)