use std::future::Future;
use std::net::SocketAddr;
use std::pin::Pin;
use std::sync::Arc;
use bon::Builder;
use conflaguration::Settings;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use proxima_http::http3::native::H3NativeUpstream;
use proxima_primitives::pipe::handler::{PipeHandle, into_handle};
use proxima_primitives::pipe::pipe_factory::PipeFactory;
use crate::error::ProximaError;
#[derive(Debug, Default)]
pub struct H3NativeUpstreamFactory;
impl H3NativeUpstreamFactory {
#[must_use]
pub fn new() -> Self {
Self
}
}
impl PipeFactory for H3NativeUpstreamFactory {
fn name(&self) -> &str {
"h3-native"
}
fn build(
&self,
spec: &Value,
_inner: Option<PipeHandle>,
) -> Pin<Box<dyn Future<Output = Result<PipeHandle, ProximaError>> + Send + '_>> {
let spec = spec.clone();
Box::pin(async move {
let config: H3NativeConfig = serde_json::from_value(spec)
.map_err(|err| ProximaError::Config(format!("h3-native config: {err}")))?;
let dial = config.into_dial()?;
let mut upstream = if dial.insecure {
H3NativeUpstream::with_client_config(
dial.addr,
dial.server_name,
insecure_client_config(),
)
} else {
H3NativeUpstream::new(dial.addr, dial.server_name)
};
if let Some(ms) = dial.timeout_ms {
upstream = upstream.with_timeout(std::time::Duration::from_millis(ms));
}
Ok(into_handle(upstream))
})
}
}
#[derive(Debug)]
pub struct Dial {
addr: SocketAddr,
server_name: String,
insecure: bool,
timeout_ms: Option<u64>,
}
#[derive(Debug, Clone, Default, Builder, Deserialize, Serialize, Settings)]
#[settings(prefix = "PROXIMA_H3_NATIVE")]
#[builder(derive(Clone, Debug), on(String, into))]
pub struct H3NativeConfig {
#[setting(default)]
#[serde(default)]
pub addr: Option<String>,
#[setting(default)]
#[serde(default)]
pub server_name: Option<String>,
#[setting(default)]
#[serde(default)]
pub url: Option<String>,
#[setting(default)]
#[serde(default)]
#[builder(default)]
pub insecure: bool,
#[setting(default)]
#[serde(default)]
pub timeout_ms: Option<u64>,
}
impl H3NativeConfig {
pub fn into_dial(self) -> Result<Dial, ProximaError> {
if let Some(addr) = &self.addr {
let socket_addr: SocketAddr = addr
.parse()
.map_err(|err| ProximaError::Config(format!("h3-native addr: {err}")))?;
let server_name = self
.server_name
.clone()
.unwrap_or_else(|| socket_addr.ip().to_string());
return Ok(Dial {
addr: socket_addr,
server_name,
insecure: self.insecure,
timeout_ms: self.timeout_ms,
});
}
if let Some(url) = &self.url {
let (host, port) = parse_https_authority(url)?;
let ip = host.parse::<std::net::IpAddr>().map_err(|_| {
ProximaError::Config(format!(
"h3-native url host '{host}' is not an IP literal; use explicit `addr` until hostname resolution lands"
))
})?;
return Ok(Dial {
addr: SocketAddr::new(ip, port),
server_name: host,
insecure: self.insecure,
timeout_ms: self.timeout_ms,
});
}
Err(ProximaError::Config(
"h3-native spec needs `addr` (+ optional `server_name`) or `url`".to_string(),
))
}
}
fn parse_https_authority(url: &str) -> Result<(String, u16), ProximaError> {
let rest = url
.strip_prefix("https://")
.or_else(|| url.strip_prefix("http://"))
.unwrap_or(url);
let authority = rest.split(['/', '?', '#']).next().unwrap_or(rest);
match authority.rsplit_once(':') {
Some((host, port)) => {
let port: u16 = port
.parse()
.map_err(|err| ProximaError::Config(format!("h3-native url port: {err}")))?;
Ok((host.to_string(), port))
}
None => Ok((authority.to_string(), 443)),
}
}
fn insecure_client_config() -> rustls::ClientConfig {
let mut config =
rustls::ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS13])
.dangerous()
.with_custom_certificate_verifier(Arc::new(AcceptAnyCert))
.with_no_client_auth();
config.alpn_protocols = vec![b"h3".to_vec()];
config
}
#[derive(Debug)]
struct AcceptAnyCert;
impl rustls::client::danger::ServerCertVerifier for AcceptAnyCert {
fn verify_server_cert(
&self,
_end_entity: &rustls::pki_types::CertificateDer<'_>,
_intermediates: &[rustls::pki_types::CertificateDer<'_>],
_server_name: &rustls::pki_types::ServerName<'_>,
_ocsp_response: &[u8],
_now: rustls::pki_types::UnixTime,
) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
Ok(rustls::client::danger::ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &rustls::pki_types::CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &rustls::pki_types::CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
rustls::crypto::aws_lc_rs::default_provider()
.signature_verification_algorithms
.supported_schemes()
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
fn dial_from_spec(spec: &Value) -> Result<Dial, ProximaError> {
let config: H3NativeConfig = serde_json::from_value(spec.clone()).expect("config");
config.into_dial()
}
#[test]
fn factory_name_is_the_spec_key() {
assert_eq!(H3NativeUpstreamFactory::new().name(), "h3-native");
}
#[test]
fn dial_from_explicit_addr() {
let spec = serde_json::json!({
"type": "h3-native", "addr": "127.0.0.1:4433", "server_name": "localhost"
});
let dial = dial_from_spec(&spec).expect("dial");
assert_eq!(dial.addr, "127.0.0.1:4433".parse().unwrap());
assert_eq!(dial.server_name, "localhost");
assert!(!dial.insecure);
}
#[test]
fn dial_from_url_ip_literal() {
let spec = serde_json::json!({ "type": "h3-native", "url": "https://10.0.0.5:8443", "insecure": true });
let dial = dial_from_spec(&spec).expect("dial");
assert_eq!(dial.addr, "10.0.0.5:8443".parse().unwrap());
assert_eq!(dial.server_name, "10.0.0.5");
assert!(dial.insecure);
}
#[test]
fn dial_url_hostname_errs_until_resolution_lands() {
let spec = serde_json::json!({ "type": "h3-native", "url": "https://example.com:443" });
let err = dial_from_spec(&spec).unwrap_err();
assert!(format!("{err}").contains("not an IP literal"));
}
#[test]
fn dial_carries_timeout_ms_config_surface() {
let spec = serde_json::json!({
"type": "h3-native", "addr": "127.0.0.1:4433", "timeout_ms": 1500
});
let dial = dial_from_spec(&spec).expect("dial");
assert_eq!(dial.timeout_ms, Some(1500));
let bare = serde_json::json!({ "type": "h3-native", "addr": "127.0.0.1:4433" });
assert_eq!(dial_from_spec(&bare).expect("dial").timeout_ms, None);
}
#[test]
fn dial_missing_target_errs() {
let spec = serde_json::json!({ "type": "h3-native" });
assert!(dial_from_spec(&spec).is_err());
}
#[test]
fn parity_fluent_builder_and_config_value_match() {
let from_value: H3NativeConfig = serde_json::from_value(serde_json::json!({
"addr": "127.0.0.1:4433",
"server_name": "localhost",
"insecure": true,
"timeout_ms": 1500,
}))
.expect("from_value");
let from_value = from_value.into_dial().expect("dial value");
let from_builder = H3NativeConfig::builder()
.addr("127.0.0.1:4433")
.server_name("localhost")
.insecure(true)
.timeout_ms(1500)
.build()
.into_dial()
.expect("dial builder");
assert_eq!(from_value.addr, from_builder.addr);
assert_eq!(from_value.server_name, from_builder.server_name);
assert_eq!(from_value.insecure, from_builder.insecure);
assert_eq!(from_value.timeout_ms, from_builder.timeout_ms);
}
}