pontifex 1.1.2

An abstraction for building and interacting with AWS Nitro enclaves.
Documentation
use std::time::Duration;

use hyper::Client;
use hyper_rustls::HttpsConnector;
use tokio_vsock::VsockAddr;

use crate::utils::http::{vsock_proxy, vsock_proxy_http2_only};

// Re-export VSockClientBuilder for public use
pub use crate::utils::http::VSockClientBuilder;

/// The CID of the vsock proxy.
pub const VSOCK_PROXY_CID: u32 = 3;

/// A HTTP client that tunnels all requests through the host's vsock proxy.
pub type HttpClient = Client<HttpsConnector<VSockClientBuilder>>;

#[must_use]
/// Creates an HTTPS client that tunnels all requests through the host's vsock proxy.
///
/// Inside a Nitro Enclave, the parent instance (host) is reachable at CID 3. This
/// client establishes TLS to the upstream server over that vsock stream.
/// The host-side vsock proxy forwards raw bytes to the intended
/// TCP destination.
///
/// TLS is handled by rustls using the webpki root store. There is no client
/// authentication or certificate pinning by default. SNI and hostname
/// verification are derived from the request's URI, and TLS terminates at the
/// upstream service (e.g., `api.example.com` or AWS KMS), not on the proxy.
///
/// Example usage (generic HTTPS request):
/// ```rust,ignore
/// use hyper::{Client, Request, Body, body::to_bytes};
/// use pontifex::http;
///
/// #[tokio::main]
/// async fn main() -> anyhow::Result<()> {
///     // The port where your host's vsock proxy listens
///     let client: Client<_> = http::client(8000);
///
///     let req = Request::builder()
///         .method("GET")
///         .uri("https://api.example.com/v1/ping")
///         .body(Body::empty())?;
///
///     let res = client.request(req).await?;
///     let body = to_bytes(res).await?;
///     println!("{}", String::from_utf8_lossy(&body));
///     Ok(())
/// }
/// ```
///
/// Notes:
/// - For non-AWS APIs, add your own auth headers/tokens to requests.
/// - For AWS services, prefer the typed AWS SDK clients and configure them to
///   use this HTTPS-over-vsock transport (see `kms::client`).
/// - The connector ignores the dial target from the URI and always connects to
///   the fixed vsock address (CID 3 + `vsock_proxy_port`), while preserving
///   Host/SNI for end-to-end TLS to the upstream.
pub fn client(vsock_proxy_port: u32) -> HttpClient {
	Client::builder()
		.http2_only(true)
		.http2_adaptive_window(false)  // Prevent large window updates
		.http2_keep_alive_interval(Some(Duration::from_secs(30)))
		.http2_keep_alive_timeout(Duration::from_secs(10))
		.build(vsock_proxy(VsockAddr::new(
			VSOCK_PROXY_CID,
			vsock_proxy_port,
		)))
}

/// Configuration for an HTTPS client that tunnels all requests through the host's vsock proxy and only uses HTTP/2.
pub struct Http2ClientConfig {
	initial_stream_window_size: Option<u32>,
	initial_connection_window_size: Option<u32>,
	adaptive_window: bool,
	keep_alive_interval: Option<Duration>,
	keep_alive_timeout: Duration,
}

impl Default for Http2ClientConfig {
	fn default() -> Self {
		Self {
			initial_stream_window_size: None,
			initial_connection_window_size: None,
			adaptive_window: false,
			keep_alive_interval: None,
			// Hyper's default is 20 seconds
			keep_alive_timeout: Duration::from_secs(20),
		}
	}
}

/// Creates an HTTPS client that tunnels all requests through the host's vsock proxy and only uses HTTP/2.
#[must_use]
pub fn client_http2_only(vsock_proxy_port: u32, config: &Http2ClientConfig) -> HttpClient {
	Client::builder()
		.http2_only(true)
		.http2_adaptive_window(config.adaptive_window)
		.http2_keep_alive_interval(config.keep_alive_interval)
		.http2_keep_alive_timeout(config.keep_alive_timeout)
		.http2_initial_stream_window_size(config.initial_stream_window_size)
		.http2_initial_connection_window_size(config.initial_connection_window_size)
		.build(vsock_proxy_http2_only(VsockAddr::new(
			VSOCK_PROXY_CID,
			vsock_proxy_port,
		)))
}