use std::sync::{Arc, OnceLock};
use crate::DriverError;
pub(crate) fn ring_provider() -> Arc<rustls::crypto::CryptoProvider> {
static PROVIDER: OnceLock<Arc<rustls::crypto::CryptoProvider>> = OnceLock::new();
PROVIDER
.get_or_init(|| Arc::new(rustls::crypto::ring::default_provider()))
.clone()
}
pub(crate) fn default_client_config() -> Arc<rustls::ClientConfig> {
static CONFIG: OnceLock<Arc<rustls::ClientConfig>> = OnceLock::new();
CONFIG
.get_or_init(|| {
let mut root_store = rustls::RootCertStore::empty();
root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
let config = build_client_config(root_store, None).expect(
"bsql: default rustls ClientConfig must build with webpki roots \
and the ring provider — this is a programmer error",
);
Arc::new(config)
})
.clone()
}
pub(crate) fn build_client_config(
root_store: rustls::RootCertStore,
client_auth: Option<(
Vec<rustls::pki_types::CertificateDer<'static>>,
rustls::pki_types::PrivateKeyDer<'static>,
)>,
) -> Result<rustls::ClientConfig, DriverError> {
let builder = rustls::ClientConfig::builder_with_provider(ring_provider())
.with_safe_default_protocol_versions()
.map_err(|e| {
DriverError::Protocol(format!(
"rustls: ring provider rejected default protocol versions: {e} \
(this is a bsql/rustls bug, please file an issue)"
))
})?
.with_root_certificates(root_store);
match client_auth {
Some((certs, key)) => builder.with_client_auth_cert(certs, key).map_err(|e| {
DriverError::Protocol(format!("failed to configure client certificate auth: {e}"))
}),
None => Ok(builder.with_no_client_auth()),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ring_provider_is_cached_across_calls() {
let a = ring_provider();
let b = ring_provider();
assert!(
Arc::ptr_eq(&a, &b),
"ring_provider() must return the same Arc across calls — \
OnceLock caching is the whole point"
);
assert!(Arc::strong_count(&a) >= 3);
}
#[test]
fn default_client_config_is_cached_across_calls() {
let a = default_client_config();
let b = default_client_config();
assert!(
Arc::ptr_eq(&a, &b),
"default_client_config() must return the same Arc across calls"
);
}
#[test]
fn build_client_config_default_does_not_panic() {
let mut root_store = rustls::RootCertStore::empty();
root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
let result = build_client_config(root_store, None);
assert!(
result.is_ok(),
"build_client_config must succeed for webpki roots + no-auth: {:?}",
result.err()
);
}
#[test]
#[cfg(feature = "feature-unification-repro")]
fn legacy_builder_panics_under_feature_unification() {
use std::panic::{catch_unwind, AssertUnwindSafe};
let mut root_store = rustls::RootCertStore::empty();
root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
let result = catch_unwind(AssertUnwindSafe(|| {
let _ = rustls::ClientConfig::builder()
.with_root_certificates(root_store)
.with_no_client_auth();
}));
assert!(
result.is_err(),
"rustls::ClientConfig::builder() was expected to panic under \
feature unification (both 'ring' and 'aws-lc-rs' on rustls). \
If this assertion fails, the dev-dep setup isn't reproducing \
the conflict and the positive regression tests above are \
false-green — meaning the fix may not actually protect against \
the panic in the wild."
);
}
}