use std::sync::Arc;
use bytes::Bytes;
use crate::models::{ALPN, ConnectionID, Limits, Role, Version};
use crate::tls::Security;
use crate::protocol::base::{AnyConnection, Connection, Transport};
use crate::protocol::common::{Buffer, Error};
use crate::protocol::h1::H1Connection;
use crate::protocol::h2::{self, H2Connection};
use crate::protocol::h3::{H3Connection, H3Session};
use crate::protocol::quic::{Handshake, QUICApplication as QUICApplicationTrait, QUICConnection, QUICError, QUICHandshake, QUICIncoming, QUICOutcome};
use crate::helpers::sync;
pub struct QUICApplication<W> {
pub versions: Vec<Version>,
pub version: Version,
pub worker: W,
}
impl<W: QUICApplicationTrait> QUICApplication<W> {
pub fn new(versions: Vec<Version>, version: Version, worker: W) -> Self {
Self { versions, version, worker }
}
}
impl<W: QUICApplicationTrait> QUICApplicationTrait for QUICApplication<W> {
fn on_conn_established(&mut self, qconn: &mut QUICConnection, handshake: &QUICHandshake) -> QUICOutcome<()> {
let negotiated = Handshake::of(qconn).negotiated(&self.versions).map_err(|error| Box::new(error) as QUICError)?;
if negotiated != self.version {
let error = Error::Version(format!("the peer selected {negotiated}, which this connection does not speak"));
return Err(Box::new(error));
}
self.worker.on_conn_established(qconn, handshake)
}
fn should_act(&self) -> bool {
self.worker.should_act()
}
fn buffer(&mut self) -> &mut [u8] {
self.worker.buffer()
}
async fn wait_for_data(&mut self, qconn: &mut QUICConnection) -> QUICOutcome<()> {
self.worker.wait_for_data(qconn).await
}
fn process_reads(&mut self, qconn: &mut QUICConnection) -> QUICOutcome<()> {
self.worker.process_reads(qconn)
}
fn process_writes(&mut self, qconn: &mut QUICConnection) -> QUICOutcome<()> {
self.worker.process_writes(qconn)
}
}
#[allow(clippy::large_enum_variant)]
pub enum Incoming {
Stream {
transport: Box<dyn Transport>,
id: ConnectionID,
client: Option<std::net::SocketAddr>,
},
QUIC(QUICIncoming),
}
impl Incoming {
pub fn client(&self) -> Option<std::net::SocketAddr> {
match self {
Self::Stream { client, .. } => *client,
Self::QUIC(incoming) => Some(incoming.peer_addr()),
}
}
}
#[derive(Clone)]
pub struct Negotiation {
pub versions: Vec<Version>,
pub limits: Limits,
pub acceptor: Option<Arc<boring::ssl::SslAcceptor>>,
pub response_finalizer: crate::finalizer::ResponseFinalizer,
}
impl Negotiation {
pub async fn accept(&self, incoming: Incoming) -> Result<AnyConnection, Error> {
match incoming {
Incoming::Stream { transport, id, client } => {
let assembling = std::pin::pin!(self.assemble(transport, id, client));
sync::Timeout::within(self.limits.handshake_timeout, assembling).await?
}
Incoming::QUIC(incoming) => self.assemble_quic(incoming),
}
}
pub fn refuse(&self, incoming: Incoming) {
let Incoming::QUIC(incoming) = incoming else {
return;
};
if let Ok(mut connection) = self.assemble_quic(incoming) {
tokio::spawn(async move { connection.close().await });
}
}
pub fn assemble_quic(&self, incoming: QUICIncoming) -> Result<AnyConnection, Error> {
let client = incoming.peer_addr();
let id = ConnectionID(Bytes::from(client.to_string()));
let Some(version) = self.versions.first().copied() else {
return Err(Error::Version("this port offers no version".into()));
};
match version {
Version::V3_0 => {
let session = H3Session::new(Role::Origin, id, self.limits).with_client(Some(client));
let (connection, worker) = H3Connection::pair(session);
let connection = connection.with_response_finalizer(self.response_finalizer);
let application = QUICApplication::new(self.versions.clone(), version, worker);
let quic = incoming.start(application);
Ok(AnyConnection::H3(connection.with_guard(Arc::new(quic))))
}
Version::V1_0 | Version::V1_1 | Version::V2_0 => Err(Error::Version(format!("{version} needs a stream transport"))),
}
}
pub async fn assemble(&self, transport: Box<dyn Transport>, id: ConnectionID, client: Option<std::net::SocketAddr>) -> Result<AnyConnection, Error> {
let Some(acceptor) = &self.acceptor else {
return self.assemble_plain(transport, id, client).await;
};
let stream = tokio_boring::accept(acceptor, transport).await.map_err(|err| Error::TLS(err.to_string()))?;
let version = ALPN::negotiated(stream.ssl().selected_alpn_protocol(), &self.versions)?;
let security = Security::of(stream.ssl());
let transport = Box::new(stream) as Box<dyn Transport>;
match version {
Version::V1_0 | Version::V1_1 => {
let connection = H1Connection::new(transport, Role::Origin, id, self.limits).with_version(version).with_response_finalizer(self.response_finalizer).with_security(security).with_client(client);
Ok(AnyConnection::H1(connection))
}
Version::V2_0 => {
let connection = H2Connection::new(transport, Role::Origin, id, self.limits).with_response_finalizer(self.response_finalizer).with_security(security).with_client(client);
Ok(AnyConnection::H2(connection))
}
Version::V3_0 => Err(Error::Version("HTTP/3 needs a QUIC port".into())),
}
}
pub async fn assemble_plain(&self, mut transport: Box<dyn Transport>, id: ConnectionID, client: Option<std::net::SocketAddr>) -> Result<AnyConnection, Error> {
let mut buffer = Buffer::with_chunk_size(self.limits.read_chunk_size as usize);
let probe = h2::PREFACE.len().min(4);
while buffer.len() < probe && buffer.fill(&mut transport, self.limits.handshake_timeout).await? {}
let sniffed = buffer.len().min(probe);
let h2 = self.versions.contains(&Version::V2_0)
&& sniffed > 0
&& buffer.as_slice()[..sniffed] == h2::PREFACE[..sniffed];
if h2 {
let connection = H2Connection::resume(transport, Role::Origin, id, self.limits, buffer)
.with_response_finalizer(self.response_finalizer)
.with_client(client);
return Ok(AnyConnection::H2(connection));
}
let Some(version) = self.versions.iter().copied().find(|version| version.major() == 1) else {
return Err(Error::Version("the peer sent no HTTP/2 preface and this port offers no HTTP/1.x".into()));
};
let connection = H1Connection::resume(transport, Role::Origin, id, self.limits, buffer)
.with_version(version)
.with_response_finalizer(self.response_finalizer)
.with_client(client);
Ok(AnyConnection::H1(connection))
}
}