use std::{fmt, sync::Arc, time::Duration};
use crate::{
DEFAULT_MAX_UDP_PAYLOAD_SIZE,
quic::cert_verifier::{self, CertVerifier, RejectionReport},
};
const DEFAULT_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(5);
const DEFAULT_IDLE_TIMEOUT: Duration = Duration::from_secs(30);
#[derive(Clone)]
pub struct QuicConfig {
pub handshake_timeout: Duration,
pub idle_timeout: Duration,
pub max_udp_payload_size: usize,
pub application_protos: Vec<Vec<u8>>,
pub verify_peer: bool,
pub ca_certs_directory: Option<String>,
pub ca_certs_file: Option<String>,
pub ca_certs_pem: Option<Vec<u8>>,
pub cert_verifier: Option<Arc<dyn CertVerifier>>,
pub verify_algorithm_prefs: Option<Vec<u16>>,
pub initial_max_data: u64,
pub initial_max_stream_data_bidi_local: u64,
pub initial_max_stream_data_bidi_remote: u64,
pub initial_max_stream_data_uni: u64,
pub initial_max_streams_bidi: u64,
pub initial_max_streams_uni: u64,
}
impl Default for QuicConfig {
fn default() -> Self {
Self {
handshake_timeout: DEFAULT_HANDSHAKE_TIMEOUT,
idle_timeout: DEFAULT_IDLE_TIMEOUT,
max_udp_payload_size: DEFAULT_MAX_UDP_PAYLOAD_SIZE,
application_protos: vec![b"h3".to_vec()],
verify_peer: true,
ca_certs_directory: None,
ca_certs_file: None,
ca_certs_pem: None,
cert_verifier: None,
verify_algorithm_prefs: None,
initial_max_data: 10_000_000,
initial_max_stream_data_bidi_local: 1_000_000,
initial_max_stream_data_bidi_remote: 1_000_000,
initial_max_stream_data_uni: 1_000_000,
initial_max_streams_bidi: 100,
initial_max_streams_uni: 100,
}
}
}
impl fmt::Debug for QuicConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("QuicConfig")
.field("handshake_timeout", &self.handshake_timeout)
.field("idle_timeout", &self.idle_timeout)
.field("max_udp_payload_size", &self.max_udp_payload_size)
.field("application_protos", &self.application_protos)
.field("verify_peer", &self.verify_peer)
.field("ca_certs_directory", &self.ca_certs_directory)
.field("ca_certs_file", &self.ca_certs_file)
.field("ca_certs_pem", &self.ca_certs_pem)
.field(
"cert_verifier",
&self.cert_verifier.as_ref().map(|_| "<cert verifier>"),
)
.field("verify_algorithm_prefs", &self.verify_algorithm_prefs)
.field("initial_max_data", &self.initial_max_data)
.field(
"initial_max_stream_data_bidi_local",
&self.initial_max_stream_data_bidi_local,
)
.field(
"initial_max_stream_data_bidi_remote",
&self.initial_max_stream_data_bidi_remote,
)
.field(
"initial_max_stream_data_uni",
&self.initial_max_stream_data_uni,
)
.field("initial_max_streams_bidi", &self.initial_max_streams_bidi)
.field("initial_max_streams_uni", &self.initial_max_streams_uni)
.finish()
}
}
impl QuicConfig {
pub fn builder() -> QuicConfigBuilder {
QuicConfigBuilder::default()
}
pub fn to_quiche_config(&self) -> Result<squiche::Config, squiche::Error> {
self.to_quiche_config_reporting().map(|(config, _)| config)
}
pub(crate) fn to_quiche_config_reporting(
&self,
) -> Result<(squiche::Config, Option<RejectionReport>), squiche::Error> {
let mut config = squiche::Config::new(squiche::SCION_PROTOCOL_VERSION)?;
config.set_application_protos(
&self
.application_protos
.iter()
.map(|p| p.as_slice())
.collect::<Vec<_>>(),
)?;
if let Some(ca_certs_dir) = &self.ca_certs_directory {
config.load_verify_locations_from_directory(ca_certs_dir)?;
}
if let Some(ca_certs_file) = &self.ca_certs_file {
config.load_verify_locations_from_file(ca_certs_file)?;
}
if let Some(ca_certs_pem) = &self.ca_certs_pem {
config.load_verify_locations_from_memory(ca_certs_pem)?;
}
let report =
(self.cert_verifier.is_some() && self.verify_peer).then(RejectionReport::default);
if let Some(verifier) = &self.cert_verifier {
config.set_certificate_verifier(cert_verifier::to_squiche_verifier(
verifier,
report.clone(),
))?;
}
config.set_max_idle_timeout(self.idle_timeout.as_millis() as u64);
config.set_max_recv_udp_payload_size(self.max_udp_payload_size);
config.set_max_send_udp_payload_size(self.max_udp_payload_size);
config.set_initial_max_data(self.initial_max_data);
config.set_initial_max_stream_data_bidi_local(self.initial_max_stream_data_bidi_local);
config.set_initial_max_stream_data_bidi_remote(self.initial_max_stream_data_bidi_remote);
config.set_initial_max_stream_data_uni(self.initial_max_stream_data_uni);
config.set_initial_max_streams_bidi(self.initial_max_streams_bidi);
config.set_initial_max_streams_uni(self.initial_max_streams_uni);
config.set_disable_active_migration(true);
config.verify_peer(self.verify_peer);
if let Some(prefs) = &self.verify_algorithm_prefs {
config.set_verify_algorithm_prefs(prefs)?;
}
Ok((config, report))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::quic::cert_verifier::{CertRejected, PeerCertificates};
fn rejecting_verifier() -> impl CertVerifier {
|_: &PeerCertificates<'_>| Err(CertRejected::new("no thanks"))
}
#[test]
fn a_report_exists_only_where_a_rejection_can_fail_the_handshake() {
let (_, report) = QuicConfig::default().to_quiche_config_reporting().unwrap();
assert!(report.is_none());
let (_, report) = QuicConfig::builder()
.verify_peer(true)
.with_cert_verifier(rejecting_verifier())
.build()
.to_quiche_config_reporting()
.unwrap();
assert!(report.is_some());
let (_, report) = QuicConfig::builder()
.verify_peer(false)
.with_cert_verifier(rejecting_verifier())
.build()
.to_quiche_config_reporting()
.unwrap();
assert!(report.is_none());
}
#[cfg(target_vendor = "apple")]
#[test]
fn with_platform_verifier_installs_a_verifier() {
let config = QuicConfig::builder().with_platform_verifier().build();
assert!(config.cert_verifier.is_some());
}
#[cfg(not(target_vendor = "apple"))]
#[test]
fn with_platform_verifier_has_no_effect() {
let config = QuicConfig::builder().with_platform_verifier().build();
assert!(config.cert_verifier.is_none());
let config = QuicConfig::builder()
.with_cert_verifier(rejecting_verifier())
.with_platform_verifier()
.build();
assert!(config.cert_verifier.is_some());
}
}
#[cfg(target_vendor = "apple")]
fn platform_verifier() -> Option<Arc<dyn CertVerifier>> {
Some(Arc::new(cert_verifier::apple::PlatformVerifier::new()))
}
#[cfg(not(target_vendor = "apple"))]
fn platform_verifier() -> Option<Arc<dyn CertVerifier>> {
None
}
#[derive(Debug, Default)]
pub struct QuicConfigBuilder {
config: QuicConfig,
}
impl QuicConfigBuilder {
pub fn handshake_timeout(mut self, timeout: Duration) -> Self {
self.config.handshake_timeout = timeout;
self
}
pub fn idle_timeout(mut self, timeout: Duration) -> Self {
self.config.idle_timeout = timeout;
self
}
pub fn max_udp_payload_size(mut self, size: usize) -> Self {
self.config.max_udp_payload_size = size;
self
}
pub fn application_protos(mut self, protos: Vec<Vec<u8>>) -> Self {
self.config.application_protos = protos;
self
}
pub fn verify_peer(mut self, verify: bool) -> Self {
self.config.verify_peer = verify;
self
}
pub fn ca_certs_dir(mut self, path: impl Into<String>) -> Self {
self.config.ca_certs_directory = Some(path.into());
self
}
pub fn ca_certs_file(mut self, path: impl Into<String>) -> Self {
self.config.ca_certs_file = Some(path.into());
self
}
pub fn ca_certs_pem(mut self, pem: impl Into<Vec<u8>>) -> Self {
self.config.ca_certs_pem = Some(pem.into());
self
}
#[must_use]
pub fn with_cert_verifier(mut self, verifier: impl CertVerifier) -> Self {
self.config.cert_verifier = Some(Arc::new(verifier));
self
}
#[must_use]
pub fn with_platform_verifier(mut self) -> Self {
if let Some(verifier) = platform_verifier() {
self.config.cert_verifier = Some(verifier);
}
self
}
pub fn verify_algorithm_prefs(mut self, prefs: Vec<u16>) -> Self {
self.config.verify_algorithm_prefs = Some(prefs);
self
}
pub fn build(self) -> QuicConfig {
self.config
}
}