use std::fmt;
#[cfg(native)]
use std::sync::Arc;
#[cfg(http3)]
use std::{future::Future, pin::Pin};
#[cfg(native)]
use rustls_pki_types::{CertificateDer, pem::PemObject};
use crate::Error;
#[cfg(android_verifier)]
mod android;
#[cfg(feature = "curl-backend")]
mod ca_bundle;
#[cfg(connector)]
pub(crate) mod connect;
#[cfg(http3)]
pub(crate) mod dns;
#[cfg(connector)]
pub(crate) mod happy_eyeballs;
#[cfg(connector)]
pub(crate) mod hyper_io;
#[cfg(tls_native)]
pub(crate) mod native_tls_stream;
#[cfg(all(connector, feature = "hyper-backend"))]
pub(crate) mod pool;
#[cfg(native)]
pub mod proxy;
#[cfg(http3)]
pub(crate) mod quic;
#[cfg(connector)]
mod socks5;
#[cfg(connector)]
pub(crate) mod stream;
#[cfg(tls_engine)]
pub(crate) mod tls;
#[cfg(connector)]
mod tunnel;
#[cfg(native)]
pub use proxy::{Proxy, ProxyBuilder};
#[cfg(http3)]
pub(crate) type Spawn = Arc<dyn Fn(Pin<Box<dyn Future<Output = ()> + Send>>) + Send + Sync>;
#[derive(Clone)]
pub struct Transport {
#[cfg(native)]
inner: Arc<Inner>,
}
#[cfg(native)]
struct Inner {
proxy: Proxy,
extra_roots: Vec<CertificateDer<'static>>,
#[cfg(feature = "curl-backend")]
ca_bundle: Option<Vec<u8>>,
#[cfg(tls_engine)]
tls: tls::TlsConnector,
#[cfg(all(connector, feature = "hyper-backend"))]
pool: pool::Pool,
#[cfg(http3)]
quic: once_cell::sync::OnceCell<quinn::Endpoint>,
#[cfg(http3)]
dns: dns::Config,
#[cfg(all(http3, not(target_os = "android")))]
resolver: once_cell::sync::OnceCell<dns::HickoryResolver>,
}
impl Transport {
#[must_use]
pub fn system() -> Self {
#[cfg(native)]
{
static SYSTEM: std::sync::OnceLock<Transport> = std::sync::OnceLock::new();
SYSTEM
.get_or_init(|| {
Self::builder().build().unwrap_or_else(|error| {
panic!("zenwave: the system transport cannot be initialised: {error}")
})
})
.clone()
}
#[cfg(not(native))]
{
Self {}
}
}
#[must_use]
pub fn builder() -> TransportBuilder {
TransportBuilder::default()
}
#[cfg(native)]
#[allow(dead_code)] pub(crate) fn proxy(&self) -> &Proxy {
&self.inner.proxy
}
#[cfg(native)]
#[allow(dead_code)] pub(crate) fn extra_roots(&self) -> &[CertificateDer<'static>] {
&self.inner.extra_roots
}
#[cfg(tls_engine)]
pub(crate) fn tls(&self) -> &tls::TlsConnector {
&self.inner.tls
}
#[cfg(feature = "curl-backend")]
pub(crate) fn ca_bundle(&self) -> Option<&[u8]> {
self.inner.ca_bundle.as_deref()
}
#[cfg(all(connector, feature = "hyper-backend"))]
pub(crate) fn pool(&self) -> &pool::Pool {
&self.inner.pool
}
#[cfg(http3)]
pub(crate) fn quic_endpoint(&self, spawn: Spawn) -> Result<&quinn::Endpoint, Error> {
self.inner.quic.get_or_try_init(|| quic::endpoint(spawn))
}
#[cfg(http3)]
pub(crate) async fn https_record(
&self,
spawn: Spawn,
host: &str,
port: u16,
) -> Result<Option<dns::HttpsRecord>, Error> {
dns::https_record(&self.inner, spawn, host, port).await
}
}
impl fmt::Debug for Transport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut debug = f.debug_struct("Transport");
#[cfg(native)]
debug
.field("proxy", &self.inner.proxy)
.field("extra_roots", &self.inner.extra_roots.len());
debug.finish_non_exhaustive()
}
}
#[derive(Default)]
pub struct TransportBuilder {
#[cfg(native)]
proxy: Option<Proxy>,
#[cfg(native)]
extra_roots: Vec<CertificateDer<'static>>,
#[cfg(all(http3, not(target_os = "android")))]
dns: Option<dns::Config>,
}
impl fmt::Debug for TransportBuilder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut debug = f.debug_struct("TransportBuilder");
#[cfg(native)]
debug
.field("proxy", &self.proxy)
.field("extra_roots", &self.extra_roots.len());
debug.finish_non_exhaustive()
}
}
impl TransportBuilder {
#[cfg(native)]
#[must_use]
pub fn proxy(mut self, proxy: Proxy) -> Self {
self.proxy = Some(proxy);
self
}
#[cfg(native)]
pub fn extra_root_certificates_pem(mut self, pem: &[u8]) -> Result<Self, Error> {
let before = self.extra_roots.len();
for certificate in CertificateDer::pem_slice_iter(pem) {
self.extra_roots.push(certificate.map_err(Error::tls)?);
}
if self.extra_roots.len() == before {
return Err(Error::tls("PEM input holds no CERTIFICATE block"));
}
Ok(self)
}
#[cfg(native)]
#[must_use]
pub fn extra_root_certificate_der(mut self, der: impl Into<Vec<u8>>) -> Self {
self.extra_roots.push(CertificateDer::from(der.into()));
self
}
#[cfg(all(http3, not(target_os = "android")))]
#[allow(dead_code)] #[must_use]
pub(crate) fn dns_config(mut self, config: dns::Config) -> Self {
self.dns = Some(config);
self
}
pub fn build(self) -> Result<Transport, Error> {
#[cfg(native)]
{
#[cfg(tls_engine)]
let tls = tls::TlsConnector::new(&self.extra_roots)?;
#[cfg(feature = "curl-backend")]
let ca_bundle = if self.extra_roots.is_empty() {
None
} else {
Some(ca_bundle::platform_roots_with(&self.extra_roots)?)
};
#[cfg(all(http3, not(target_os = "android")))]
let dns = self.dns.unwrap_or_else(|| {
let config = dns::Config::system();
if let dns::Config::Unavailable(reason) = &config {
tracing::warn!("system DNS resolver configuration unavailable: {reason}");
}
config
});
#[cfg(all(http3, target_os = "android"))]
let dns = dns::Config;
Ok(Transport {
inner: Arc::new(Inner {
proxy: self.proxy.unwrap_or_else(Proxy::system),
extra_roots: self.extra_roots,
#[cfg(feature = "curl-backend")]
ca_bundle,
#[cfg(tls_engine)]
tls,
#[cfg(all(connector, feature = "hyper-backend"))]
pool: pool::Pool::new(),
#[cfg(http3)]
quic: once_cell::sync::OnceCell::new(),
#[cfg(http3)]
dns,
#[cfg(all(http3, not(target_os = "android")))]
resolver: once_cell::sync::OnceCell::new(),
}),
})
}
#[cfg(not(native))]
{
Ok(Transport {})
}
}
}