1use futures::future::{Future, FutureExt};
6use futures::io::{self, AsyncRead, AsyncWrite};
7use futures::task::{Context, Poll};
8use hyper::body::Body;
9use hyper_util::client::legacy::Client;
10use hyper_util::client::legacy::connect::{Connected, Connection};
11use hyper_util::rt::TokioIo;
12#[cfg(not(target_os = "fuchsia"))]
13use netext::MultithreadedTokioAsyncWrapper;
14use std::marker::PhantomData;
15use std::net::{AddrParseError, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
16use std::num::ParseIntError;
17use std::pin::Pin;
18use tokio::io::ReadBuf;
19
20#[cfg(not(target_os = "fuchsia"))]
21use tokio::net;
22
23#[cfg(target_os = "fuchsia")]
24use fidl_fuchsia_posix_socket as fposix_socket;
25
26#[cfg(target_os = "fuchsia")]
27use fuchsia_async::net;
28
29#[cfg(not(target_os = "fuchsia"))]
30mod not_fuchsia;
31#[cfg(not(target_os = "fuchsia"))]
32pub use not_fuchsia::*;
33
34#[cfg(target_os = "fuchsia")]
35mod fuchsia;
36#[cfg(target_os = "fuchsia")]
37pub use crate::fuchsia::*;
38
39#[cfg(target_os = "fuchsia")]
40mod happy_eyeballs;
41
42pub type HttpClient<B = http_body_util::Full<hyper::body::Bytes>> = Client<HyperConnector, B>;
44
45pub type HttpsClient<B = http_body_util::Full<hyper::body::Bytes>> =
47 Client<hyper_rustls::HttpsConnector<HyperConnector>, B>;
48
49pub trait MakeClientBuilder: Sized {
52 fn builder() -> HttpClientBuilder<Self> {
53 HttpClientBuilder::default()
54 }
55}
56
57impl<B: Body + Send> MakeClientBuilder for HttpClient<B> {}
58impl<B: Body + Send> MakeClientBuilder for HttpsClient<B> {}
59
60#[must_use = "futures do nothing unless polled"]
62pub struct HyperConnectorFuture {
63 fut: Pin<Box<dyn Future<Output = Result<TokioIo<TcpStream>, io::Error>> + Send>>,
66}
67
68impl Future for HyperConnectorFuture {
69 type Output = Result<TokioIo<TcpStream>, io::Error>;
70
71 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
72 self.fut.as_mut().poll(cx)
73 }
74}
75
76pub struct TcpStream {
77 #[cfg(target_os = "fuchsia")]
78 pub stream: net::TcpStream,
79 #[cfg(not(target_os = "fuchsia"))]
80 pub stream: MultithreadedTokioAsyncWrapper<net::TcpStream>,
81}
82
83impl tokio::io::AsyncRead for TcpStream {
84 fn poll_read(
85 mut self: Pin<&mut Self>,
86 cx: &mut Context<'_>,
87 buf: &mut ReadBuf<'_>,
88 ) -> Poll<io::Result<()>> {
89 Pin::new(&mut self.stream).poll_read(cx, buf.initialize_unfilled()).map_ok(|sz| {
90 buf.advance(sz);
91 ()
92 })
93 }
94
95 }
97
98impl tokio::io::AsyncWrite for TcpStream {
99 fn poll_write(
100 mut self: Pin<&mut Self>,
101 cx: &mut Context<'_>,
102 buf: &[u8],
103 ) -> Poll<io::Result<usize>> {
104 Pin::new(&mut self.stream).poll_write(cx, buf)
105 }
106
107 fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
108 Pin::new(&mut self.get_mut().stream).poll_flush(cx)
109 }
110
111 fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
112 Pin::new(&mut self.get_mut().stream).poll_close(cx)
113 }
114
115 }
117
118impl Connection for TcpStream {
119 fn connected(&self) -> Connected {
120 Connected::new()
121 }
122}
123
124#[non_exhaustive]
125#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
126pub struct TcpOptions {
128 pub keepalive_idle: Option<std::time::Duration>,
130 pub keepalive_interval: Option<std::time::Duration>,
132 pub keepalive_count: Option<u32>,
134 pub tcp_receive_buffer_size: Option<usize>,
136}
137
138impl TcpOptions {
139 pub fn keepalive_timeout(dur: std::time::Duration) -> Self {
147 if dur.as_secs() == 0 {
148 return TcpOptions::default();
149 }
150
151 TcpOptions {
152 keepalive_idle: dur.checked_div(2),
153 keepalive_interval: dur.checked_div(6),
154 keepalive_count: Some(3),
155 tcp_receive_buffer_size: None,
156 }
157 }
158
159 pub(crate) fn apply<T: std::os::fd::AsFd>(&self, stream: &T) -> io::Result<()> {
160 let stream = socket2::SockRef::from(stream);
161 let mut any = false;
162 let mut keepalive = socket2::TcpKeepalive::new();
163 if let Some(idle) = self.keepalive_idle {
164 any = true;
165 keepalive = keepalive.with_time(idle);
166 };
167 if let Some(interval) = self.keepalive_interval {
168 any = true;
169 keepalive = keepalive.with_interval(interval);
170 }
171 if let Some(count) = self.keepalive_count {
172 any = true;
173 keepalive = keepalive.with_retries(count);
174 }
175 if any {
176 stream.set_tcp_keepalive(&keepalive)?;
177 }
178 if let Some(size) = self.tcp_receive_buffer_size {
179 stream.set_recv_buffer_size(size)?;
180 }
181 Ok(())
182 }
183}
184
185#[derive(Clone, Debug, Default)]
188pub struct SocketOptions {
189 pub bind_device: Option<String>,
191}
192
193#[derive(Clone)]
194pub struct Executor;
195
196impl<F: Future + Send + 'static> hyper::rt::Executor<F> for Executor {
197 fn execute(&self, fut: F) {
198 fuchsia_async::Task::spawn(fut.map(|_| ())).detach()
199 }
200}
201
202#[derive(Clone)]
203pub struct LocalExecutor;
204
205#[derive(Clone)]
207pub struct HttpClientBuilder<T> {
208 tcp_options: Option<TcpOptions>,
209 socket_options: Option<SocketOptions>,
210 tls: Option<rustls::ClientConfig>,
211 phantom: PhantomData<T>,
212}
213
214impl<T> Default for HttpClientBuilder<T> {
215 fn default() -> Self {
216 Self {
217 tcp_options: Default::default(),
218 socket_options: Default::default(),
219 tls: Default::default(),
220 phantom: Default::default(),
221 }
222 }
223}
224
225impl<B: Body + Send> HttpClientBuilder<HttpClient<B>>
226where
227 B::Data: Send,
228{
229 pub fn build(mut self) -> HttpClient<B> {
231 Client::builder(Executor).build(self.connector())
232 }
233}
234
235impl<B: Body + Send> HttpClientBuilder<HttpsClient<B>>
236where
237 B::Data: Send,
238{
239 pub fn build(mut self) -> HttpsClient<B> {
241 let https = hyper_rustls::HttpsConnector::from((
242 self.connector(),
243 self.tls.unwrap_or_else(|| {
244 let root_store = new_root_cert_store();
245 rustls::ClientConfig::builder()
246 .with_root_certificates(root_store)
247 .with_no_client_auth()
248 }),
249 ));
250 Client::builder(Executor).build(https)
251 }
252
253 pub fn tls(self, tls: rustls::ClientConfig) -> Self {
255 Self { tls: Some(tls), ..self }
256 }
257}
258
259impl<T> HttpClientBuilder<T> {
260 fn connector(&mut self) -> HyperConnector {
261 HyperConnector::from((
262 self.tcp_options.take().unwrap_or_default(),
263 self.socket_options.take().unwrap_or_default(),
264 ))
265 }
266
267 pub fn tcp_options(self, tcp_options: TcpOptions) -> Self {
269 Self { tcp_options: Some(tcp_options), ..self }
270 }
271
272 pub fn socket_options(self, socket_options: SocketOptions) -> Self {
274 Self { socket_options: Some(socket_options), ..self }
275 }
276}
277
278impl<F: Future + 'static> hyper::rt::Executor<F> for LocalExecutor {
279 fn execute(&self, fut: F) {
280 fuchsia_async::Task::local(fut.map(drop)).detach()
281 }
282}
283
284pub fn new_client() -> HttpClient {
286 HttpClient::builder().build()
287}
288
289pub fn new_https_client_dangerous(
290 tls: rustls::ClientConfig,
291 tcp_options: TcpOptions,
292) -> HttpsClient {
293 HttpsClient::builder().tls(tls).tcp_options(tcp_options).build()
294}
295
296pub fn new_https_client_from_tcp_options(tcp_options: TcpOptions) -> HttpsClient {
298 HttpsClient::builder().tcp_options(tcp_options).build()
299}
300
301pub fn new_https_client() -> HttpsClient {
303 HttpsClient::builder().build()
304}
305
306pub(crate) async fn parse_ip_addr<'a, F, Fut>(
307 host: &'a str,
308 port: u16,
309 interface_name_to_index: F,
310) -> Result<Option<SocketAddr>, io::Error>
311where
312 F: Fn(&'a str) -> Fut + 'a,
313 Fut: Future<Output = Result<u32, io::Error>> + 'a,
314{
315 match host.parse::<Ipv4Addr>() {
316 Ok(addr) => {
317 return Ok(Some(SocketAddr::V4(SocketAddrV4::new(addr, port))));
318 }
319 Err(AddrParseError { .. }) => {}
320 }
321
322 if !host.starts_with("[") || !host.ends_with(']') {
324 return Ok(None);
325 }
326
327 let host = &host[1..host.len() - 1];
328
329 let (host, zone_id) = if let Some((host, zone_id)) = host.split_once("%25") {
331 (host, Some(zone_id))
332 } else {
333 (host, None)
334 };
335
336 let addr = match host.parse::<Ipv6Addr>() {
337 Ok(addr) => addr,
338 Err(AddrParseError { .. }) => {
339 return Ok(None);
340 }
341 };
342
343 let scope_id = if let Some(zone_id) = zone_id {
344 if addr.segments()[..4] != [0xfe80, 0, 0, 0] {
365 return Err(io::Error::other("zone_id is only usable with link local addresses"));
366 }
367
368 match zone_id.parse::<u32>() {
370 Ok(scope_id) => scope_id,
371 Err(ParseIntError { .. }) => interface_name_to_index(zone_id).await?,
372 }
373 } else {
374 0
375 };
376
377 Ok(Some(SocketAddr::V6(SocketAddrV6::new(addr, port, 0, scope_id))))
378}
379
380#[cfg(target_os = "fuchsia")]
381pub(crate) fn connect_and_bind_device<D: AsRef<[u8]>, T: ProviderConnector>(
382 provider: &T,
383 addr: SocketAddr,
384 bind_device: Option<D>,
385) -> io::Result<net::TcpConnector> {
386 let socket = stream_socket(
389 provider,
390 match addr {
391 SocketAddr::V4(_) => fposix_socket::Domain::Ipv4,
392 SocketAddr::V6(_) => fposix_socket::Domain::Ipv6,
393 },
394 fposix_socket::StreamSocketProtocol::Tcp,
395 )?;
396 if let Some(bind_device) = bind_device {
397 socket.bind_device(Some(bind_device.as_ref()))?;
398 }
399 net::TcpStream::connect_from_raw(socket, addr)
400}
401
402#[cfg(test)]
403mod tests {
404 use super::*;
405 use assert_matches::assert_matches;
406 use fuchsia_async::{self as fasync};
407
408 async fn unsupported(_name: &str) -> Result<u32, io::Error> {
409 panic!("should not have happened")
410 }
411
412 #[fasync::run_singlethreaded(test)]
413 async fn test_parse_ipv4_addr() {
414 let expected = "1.2.3.4:8080".parse::<SocketAddr>().unwrap();
415 assert_matches!(
416 parse_ip_addr("1.2.3.4", 8080, unsupported).await,
417 Ok(Some(addr)) if addr == expected);
418 }
419
420 #[fasync::run_singlethreaded(test)]
421 async fn test_parse_invalid_addresses() {
422 assert_matches!(parse_ip_addr("1.2.3", 8080, unsupported).await, Ok(None));
423 assert_matches!(parse_ip_addr("1.2.3.4.5", 8080, unsupported).await, Ok(None));
424 assert_matches!(parse_ip_addr("localhost", 8080, unsupported).await, Ok(None));
425 assert_matches!(parse_ip_addr("[fe80::1:2:3:4", 8080, unsupported).await, Ok(None));
426 assert_matches!(parse_ip_addr("[[fe80::1:2:3:4]", 8080, unsupported).await, Ok(None));
427 assert_matches!(parse_ip_addr("[]", 8080, unsupported).await, Ok(None));
428 }
429
430 #[fasync::run_singlethreaded(test)]
431 async fn test_parse_ipv6_addr() {
432 let expected = "[fe80::1:2:3:4]:8080".parse::<SocketAddr>().unwrap();
433 assert_matches!(
434 parse_ip_addr("[fe80::1:2:3:4]", 8080, unsupported).await,
435 Ok(Some(addr)) if addr == expected
436 );
437 }
438
439 #[fasync::run_singlethreaded(test)]
440 async fn test_parse_ipv6_addr_with_zone_must_be_local() {
441 assert_matches!(
442 parse_ip_addr("[fe81::1:2:3:4%252]", 8080, unsupported).await,
443 Err(err) if err.kind() == io::ErrorKind::Other
444 );
445 }
446}