use bytes::Bytes;
use foreign_types::ForeignTypeRef;
use http2::client::SendRequest;
use std::net::SocketAddr;
use tokio::net::TcpStream;
use crate::client::proxy::{dial_proxy, Proxy};
use crate::client::response::Response;
use crate::error::Result;
use crate::http2::configure_builder;
use crate::profile::ChromeProfile;
use crate::tls::build_connector;
pub struct QuikConnection {
pub h2: SendRequest<Bytes>,
pub profile: ChromeProfile,
}
pub async fn connect(
host: &str,
port: u16,
addr: SocketAddr,
profile: &ChromeProfile,
proxy: Option<&Proxy>,
) -> Result<QuikConnection> {
let tcp = if let Some(p) = proxy {
dial_proxy(p, host, port).await?
} else {
TcpStream::connect(addr).await?
};
let connector = build_connector(&profile.tls)?;
let mut config = connector.configure()?;
config.set_status_type(boring::ssl::StatusType::OCSP)?;
let ssl_ptr = config.as_ptr();
fn build_alps_payload(
settings: &crate::profile::SettingsFrame,
extra: &[(u16, u32)],
) -> Vec<u8> {
let entry_count = 4 + extra.len();
let mut payload = Vec::with_capacity(entry_count * 6);
payload.extend_from_slice(&1u16.to_be_bytes());
payload.extend_from_slice(&settings.header_table_size.to_be_bytes());
payload.extend_from_slice(&2u16.to_be_bytes());
payload.extend_from_slice(&(settings.enable_push as u32).to_be_bytes());
payload.extend_from_slice(&4u16.to_be_bytes());
payload.extend_from_slice(&settings.initial_window_size.to_be_bytes());
payload.extend_from_slice(&6u16.to_be_bytes());
payload.extend_from_slice(&settings.max_header_list_size.to_be_bytes());
for &(id, value) in extra {
payload.extend_from_slice(&id.to_be_bytes());
payload.extend_from_slice(&value.to_be_bytes());
}
payload
}
unsafe {
if profile.tls.enable_ech_grease {
boring_sys::SSL_set_enable_ech_grease(ssl_ptr, 1);
}
if profile.tls.alps_enabled {
let alps_data =
build_alps_payload(&profile.h2.settings, profile.tls.alps_extra_settings);
let alps_res = boring_sys::SSL_add_application_settings(
ssl_ptr,
b"h2".as_ptr(),
2,
alps_data.as_ptr(),
alps_data.len(),
);
if alps_res != 1 {
return Err(crate::error::Error::Connect(std::io::Error::other(
"failed to inject ALPS settings",
)));
}
}
}
let tls_stream = tokio_boring::connect(config, host, tcp)
.await
.map_err(|e| {
tracing::error!("TLS handshake failed: {:?}", e);
e
})?;
let mut h2_builder = http2::client::Builder::new();
configure_builder(&mut h2_builder, &profile.h2);
let (h2, connection) = h2_builder.handshake(tls_stream).await?;
tokio::spawn(async move {
if let Err(e) = connection.await {
tracing::error!("HTTP/2 connection driver failed: {:?}", e);
}
});
Ok(QuikConnection {
h2,
profile: profile.clone(),
})
}
impl QuikConnection {
pub async fn send(
&mut self,
request: http::Request<()>,
body: Option<Bytes>,
) -> Result<Response> {
let url_str = request.uri().to_string();
if let Some(data) = body {
let (response_future, mut send_stream) = self.h2.send_request(request, false)?;
send_stream.send_data(data, true)?;
let response = response_future.await?;
Ok(Response::new(response, url_str))
} else {
let (response_future, _) = self.h2.send_request(request, true)?;
let response = response_future.await?;
Ok(Response::new(response, url_str))
}
}
}