use super::framing::length_prefixed_exchange;
use crate::Client;
use async_lock::Mutex;
use std::{
borrow::Cow,
io::{self, ErrorKind},
sync::Arc,
};
use trillium_server_common::{Connector, QuicConnection, url::Url};
#[derive(Debug, Clone)]
pub(super) struct Doq {
resolver: Url,
connection: Arc<Mutex<Option<QuicConnection>>>,
}
impl Doq {
pub(super) fn new(mut resolver: Url) -> Self {
if resolver.port().is_none() {
let _ = resolver.set_port(Some(853));
}
Self {
resolver,
connection: Arc::new(Mutex::new(None)),
}
}
pub(super) fn host(&self) -> Option<&str> {
self.resolver.host_str()
}
pub(super) fn resolver(&self) -> &Url {
&self.resolver
}
pub(super) async fn exchange(&self, client: &Client, query: Vec<u8>) -> io::Result<Vec<u8>> {
log::trace!(
"DoQ exchange to {}: {}-byte query",
self.resolver,
query.len()
);
let connection = self.connection(client).await?;
match connection.open_bidi().await {
Ok((id, mut stream)) => {
log::trace!("DoQ query on bidi stream {id} to {}", self.resolver);
let response = length_prefixed_exchange(&mut stream, &query, true).await;
log::trace!(
"DoQ stream {id} to {} returned {:?}",
self.resolver,
response.as_ref().map(Vec::len)
);
response
}
Err(e) => {
log::debug!(
"DoQ connection to {} unusable ({e}); reconnecting",
self.resolver
);
self.invalidate().await;
let connection = self.connection(client).await?;
let (id, mut stream) = connection.open_bidi().await?;
log::trace!(
"DoQ query on bidi stream {id} (reconnected) to {}",
self.resolver
);
length_prefixed_exchange(&mut stream, &query, true).await
}
}
}
async fn connection(&self, client: &Client) -> io::Result<QuicConnection> {
let mut guard = self.connection.lock().await;
if let Some(connection) = guard.as_ref() {
return Ok(connection.clone());
}
let connection = self.connect(client).await?;
*guard = Some(connection.clone());
Ok(connection)
}
async fn invalidate(&self) {
*self.connection.lock().await = None;
}
async fn connect(&self, client: &Client) -> io::Result<QuicConnection> {
let h3 = client.h3().ok_or_else(|| {
io::Error::new(
ErrorKind::Unsupported,
"DoQ requires an HTTP/3-capable client",
)
})?;
let host = self.resolver.host_str().ok_or_else(|| {
io::Error::new(ErrorKind::InvalidInput, "DoQ resolver URL has no host")
})?;
let port = self.resolver.port().unwrap_or(853);
let addr = client
.connector()
.resolve(host, port)
.await?
.into_iter()
.next()
.ok_or_else(|| {
io::Error::new(ErrorKind::NotFound, "no address for DoQ resolver host")
})?;
log::debug!("DoQ connecting to {host} at {addr} (alpn doq)");
let connection = h3
.connect_with_alpn(host, addr, &[Cow::Borrowed(&b"doq"[..])])
.await?;
log::debug!("DoQ connected to {host} at {addr}");
Ok(connection)
}
}