http_quik/client/connector.rs
1use bytes::Bytes;
2use foreign_types::ForeignTypeRef;
3use http2::client::SendRequest;
4use std::net::SocketAddr;
5use tokio::net::TcpStream;
6
7use crate::client::proxy::{dial_proxy, Proxy};
8use crate::client::response::Response;
9use crate::error::Result;
10use crate::http2::configure_builder;
11use crate::profile::ChromeProfile;
12use crate::tls::build_connector;
13
14/// Represents an established HTTP/2 connection with a fixed Chrome identity.
15///
16/// This structure holds the active H2 request handle and the profile used
17/// to establish the connection. Reusing this connection ensures that all
18/// subsequent requests adhere to the same behavioral constraints (e.g.,
19/// same SETTINGS, same window increments).
20pub struct QuikConnection {
21 /// The handle used to initiate new H2 streams.
22 pub h2: SendRequest<Bytes>,
23 /// The profile used for TLS and H2 handshake parity.
24 pub profile: ChromeProfile,
25}
26
27/// Establishes a new network connection following the Chrome 134 transport pipeline.
28///
29/// This function orchestrates a multi-stage handshake to ensure the resulting
30/// connection is indistinguishable from a real browser:
31///
32/// 1. **Proxy/TCP**: Dials the target host (optionally via a SOCKS5/HTTP tunnel).
33/// 2. **TLS Handshake**: Performs a BoringSSL handshake with ClientHello permutation,
34/// GREASE, and extension shuffling.
35/// 3. **ALPS/ECH**: Injects per-connection application settings (ALPS) and ECH GREASE
36/// via raw BoringSSL FFI calls.
37/// 4. **H2 Handshake**: Negotiates the HTTP/2 session using a specialized builder that
38/// replicates Chromium's SETTINGS frame order and connection window increments.
39pub async fn connect(
40 host: &str,
41 port: u16,
42 addr: SocketAddr,
43 profile: &ChromeProfile,
44 proxy: Option<&Proxy>,
45) -> Result<QuikConnection> {
46 // Stage 1: Establish raw TCP transport.
47 let tcp = if let Some(p) = proxy {
48 dial_proxy(p, host, port).await?
49 } else {
50 TcpStream::connect(addr).await?
51 };
52
53 // Stage 2: Configure the TLS connector.
54 let connector = build_connector(&profile.tls)?;
55 let mut config = connector.configure()?;
56
57 // Request OCSP stapling to match Chrome's certificate verification behavior.
58 config.set_status_type(boring::ssl::StatusType::OCSP)?;
59
60 // Stage 3: Per-connection FFI for advanced Chrome features.
61 let ssl_ptr = config.as_ptr();
62
63 // SAFETY: The `ssl_ptr` is valid for the duration of the configuration phase.
64 // We pass valid pointers for the ALPN protocol "h2" and the static ALPS buffer.
65 // These calls are required because high-level Rust wrappers often do not yet
66 // expose the latest Chromium-specific BoringSSL features.
67 unsafe {
68 if profile.tls.enable_ech_grease {
69 boring_sys::SSL_set_enable_ech_grease(ssl_ptr, 1);
70 }
71 if profile.tls.alps_enabled {
72 // Chrome 134 ALPS H2 settings payload:
73 // ID 1: 65536, ID 2: 0, ID 4: 6291456, ID 6: 262144
74 let alps_data: [u8; 24] = [
75 0, 1, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 4, 0, 96, 0, 0, 0, 6, 0, 4, 0, 0,
76 ];
77
78 boring_sys::SSL_add_application_settings(
79 ssl_ptr,
80 b"h2".as_ptr(),
81 2,
82 alps_data.as_ptr(),
83 alps_data.len(),
84 );
85 }
86 }
87
88 // Stage 4: TLS handshake.
89 let tls_stream = tokio_boring::connect(config, host, tcp)
90 .await
91 .map_err(|e| {
92 tracing::error!("TLS handshake failed: {:?}", e);
93 e
94 })?;
95
96 // Stage 5: HTTP/2 handshake.
97 let mut h2_builder = http2::client::Builder::new();
98 configure_builder(&mut h2_builder, &profile.h2);
99
100 let (h2, connection) = h2_builder.handshake(tls_stream).await?;
101
102 // Drive the connection in the background. If this task terminates,
103 // the H2 session is considered dead.
104 tokio::spawn(async move {
105 if let Err(e) = connection.await {
106 tracing::error!("HTTP/2 connection driver failed: {:?}", e);
107 }
108 });
109
110 Ok(QuikConnection {
111 h2,
112 profile: profile.clone(),
113 })
114}
115
116impl QuikConnection {
117 /// Dispatches an HTTP request over the established H2 session.
118 pub async fn send(
119 &mut self,
120 request: http::Request<()>,
121 body: Option<Bytes>,
122 ) -> Result<Response> {
123 let url_str = request.uri().to_string();
124 if let Some(data) = body {
125 let (response_future, mut send_stream) = self.h2.send_request(request, false)?;
126 send_stream.send_data(data, true)?;
127 let response = response_future.await?;
128 Ok(Response::new(response, url_str))
129 } else {
130 let (response_future, _) = self.h2.send_request(request, true)?;
131 let response = response_future.await?;
132 Ok(Response::new(response, url_str))
133 }
134 }
135}