use std::{fmt, sync::Arc, time::Duration};
use async_trait::async_trait;
use scion_quic::quic::config::QuicConfig;
use scion_stack::{
ScionStackBuilder,
reqwest_connect_rpc::token_source::{
TokenSource, TokenSourceError, TokenSourceWatch, static_token::StaticTokenSource,
},
resolver::ScionDnsResolver,
stack::builder::PreferredUnderlay,
};
use sciparse::address::ip_addr::ScionIpAddr;
use url::Url;
pub const DEFAULT_CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
pub const DEFAULT_REQUEST_TIMEOUT: Duration = Duration::from_secs(30);
pub const DEFAULT_IDLE_CONNECTION_TIMEOUT: Duration = Duration::from_secs(25);
pub const DEFAULT_MAX_ORIGINS: usize = 8;
pub const DEFAULT_CONNECTION_ATTEMPT_DELAY: Duration = Duration::from_millis(250);
pub(crate) type StackCustomizer =
dyn Fn(ScionStackBuilder) -> ScionStackBuilder + Send + Sync + 'static;
pub(crate) struct SharedTokenSource(pub(crate) Arc<dyn TokenSource>);
#[async_trait]
impl TokenSource for SharedTokenSource {
fn watch(&self) -> TokenSourceWatch {
self.0.watch()
}
async fn get_token(&self) -> Result<String, TokenSourceError> {
self.0.get_token().await
}
}
#[derive(Clone)]
pub struct Config {
pub(crate) endhost_api: Url,
pub(crate) auth_token_source: Option<Arc<dyn TokenSource>>,
pub(crate) preferred_underlay: Option<PreferredUnderlay>,
pub(crate) stack_customizer: Option<Arc<StackCustomizer>>,
pub(crate) connect_timeout: Duration,
pub(crate) request_timeout: Duration,
pub(crate) idle_connection_timeout: Duration,
pub(crate) max_origins: usize,
pub(crate) connection_attempt_delay: Duration,
pub(crate) quic: QuicConfig,
pub(crate) resolver: Option<Arc<dyn ScionDnsResolver>>,
pub(crate) dns_overrides: Vec<(String, Vec<ScionIpAddr>)>,
}
impl Config {
#[must_use]
pub fn new(endhost_api: Url) -> Self {
Config {
endhost_api,
auth_token_source: None,
preferred_underlay: None,
stack_customizer: None,
connect_timeout: DEFAULT_CONNECT_TIMEOUT,
request_timeout: DEFAULT_REQUEST_TIMEOUT,
idle_connection_timeout: DEFAULT_IDLE_CONNECTION_TIMEOUT,
max_origins: DEFAULT_MAX_ORIGINS,
connection_attempt_delay: DEFAULT_CONNECTION_ATTEMPT_DELAY,
quic: QuicConfig::default(),
resolver: None,
dns_overrides: Vec::new(),
}
}
#[must_use]
pub fn with_auth_token(self, token: impl Into<String>) -> Self {
self.with_auth_token_source(StaticTokenSource::from(token.into()))
}
#[must_use]
pub fn with_auth_token_source(mut self, source: impl TokenSource) -> Self {
self.auth_token_source = Some(Arc::new(source));
self
}
#[must_use]
pub fn with_preferred_underlay(mut self, underlay: PreferredUnderlay) -> Self {
self.preferred_underlay = Some(underlay);
self
}
#[must_use]
pub fn with_stack_customizer(
mut self,
customizer: impl Fn(ScionStackBuilder) -> ScionStackBuilder + Send + Sync + 'static,
) -> Self {
self.stack_customizer = Some(Arc::new(customizer));
self
}
#[must_use]
pub fn with_connect_timeout(mut self, timeout: Duration) -> Self {
self.connect_timeout = timeout;
self
}
#[must_use]
pub fn with_request_timeout(mut self, timeout: Duration) -> Self {
self.request_timeout = timeout;
self
}
#[must_use]
pub fn with_idle_connection_timeout(mut self, timeout: Duration) -> Self {
self.idle_connection_timeout = timeout;
self
}
#[must_use]
pub fn with_max_origins(mut self, max_origins: usize) -> Self {
self.max_origins = max_origins;
self
}
#[must_use]
pub fn with_connection_attempt_delay(mut self, delay: Duration) -> Self {
self.connection_attempt_delay = delay;
self
}
#[must_use]
pub fn with_quic_config(mut self, quic: QuicConfig) -> Self {
self.quic = quic;
self
}
#[must_use]
pub fn with_resolver(mut self, resolver: Arc<dyn ScionDnsResolver>) -> Self {
self.resolver = Some(resolver);
self
}
#[must_use]
pub fn with_dns_override(mut self, host: impl Into<String>, addrs: Vec<ScionIpAddr>) -> Self {
self.dns_overrides
.push((host.into().to_ascii_lowercase(), addrs));
self
}
}
impl fmt::Debug for Config {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Config")
.field("endhost_api", &self.endhost_api)
.field(
"auth_token_source",
&self.auth_token_source.as_ref().map(|_| "<redacted>"),
)
.field("preferred_underlay", &self.preferred_underlay)
.field(
"stack_customizer",
&self.stack_customizer.as_ref().map(|_| "<fn>"),
)
.field("connect_timeout", &self.connect_timeout)
.field("request_timeout", &self.request_timeout)
.field("idle_connection_timeout", &self.idle_connection_timeout)
.field("max_origins", &self.max_origins)
.field("connection_attempt_delay", &self.connection_attempt_delay)
.field("quic", &self.quic)
.field("resolver", &self.resolver.as_ref().map(|_| "<custom>"))
.field("dns_overrides", &self.dns_overrides)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn config() -> Config {
Config::new("https://endhost-api.example.org".parse().unwrap())
}
#[test]
fn new_applies_defaults() {
let config = config();
assert_eq!(config.connect_timeout, DEFAULT_CONNECT_TIMEOUT);
assert_eq!(config.request_timeout, DEFAULT_REQUEST_TIMEOUT);
assert_eq!(
config.idle_connection_timeout,
DEFAULT_IDLE_CONNECTION_TIMEOUT
);
assert_eq!(config.max_origins, DEFAULT_MAX_ORIGINS);
assert_eq!(
config.connection_attempt_delay,
DEFAULT_CONNECTION_ATTEMPT_DELAY
);
}
#[test]
fn default_idle_sweep_precedes_quic_idle_timeout() {
let config = config();
assert!(config.idle_connection_timeout < config.quic.idle_timeout);
}
#[test]
fn debug_redacts_the_auth_token() {
let config = config().with_auth_token("super-secret-token");
let debug = format!("{config:?}");
assert!(!debug.contains("super-secret-token"));
assert!(debug.contains("<redacted>"));
}
}